diff --git a/man/crystal.1 b/man/crystal.1 index 5f08ce29d07e..660776450239 100644 --- a/man/crystal.1 +++ b/man/crystal.1 @@ -117,6 +117,8 @@ Generate the output without any symbolic debug symbols. Define a compile-time flag. This is useful to conditionally define types, methods, or commands based on flags available at compile time. The default flags are from the target triple given with --target-triple or the hosts default, if none is given. .It Fl -emit Op asm|llvm-bc|llvm-ir|obj Comma separated list of types of output for the compiler to emit. You can use this to see the generated LLVM IR, LLVM bitcode, assembly, and object files. +.It Fl -frame-pointers Op auto|always|non-leaf +Control the preservation of frame pointers. The default value, --frame-pointers=auto, will preserve frame pointers on debug builds and try to omit them on release builds (certain platforms require them to stay enabled). --frame-pointers=always will always preserve them, and non-leaf will only force their preservation on non-leaf functions. .It Fl f Ar text|json, Fl -format Ar text|json Format of output. Defaults to text. The json format can be used to get a more parser-friendly output. .It Fl -error-trace diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 9890454ecfef..fc646950aca3 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -69,8 +69,10 @@ module Crystal end end - def codegen(node, single_module = false, debug = Debug::Default) - visitor = CodeGenVisitor.new self, node, single_module: single_module, debug: debug + def codegen(node, single_module = false, debug = Debug::Default, + frame_pointers = FramePointers::Auto) + visitor = CodeGenVisitor.new self, node, single_module: single_module, + debug: debug, frame_pointers: frame_pointers visitor.accept node visitor.process_finished_hooks visitor.finish @@ -176,7 +178,10 @@ module Crystal @c_malloc_fun : LLVMTypedFunction? @c_realloc_fun : LLVMTypedFunction? - def initialize(@program : Program, @node : ASTNode, @single_module : Bool = false, @debug = Debug::Default) + def initialize(@program : Program, @node : ASTNode, + @single_module : Bool = false, + @debug = Debug::Default, + @frame_pointers : FramePointers = :auto) @abi = @program.target_machine.abi @llvm_context = LLVM::Context.new # LLVM::Context.register(@llvm_context, "main") diff --git a/src/compiler/crystal/codegen/fun.cr b/src/compiler/crystal/codegen/fun.cr index 9a8e51df3175..784eba83b9b9 100644 --- a/src/compiler/crystal/codegen/fun.cr +++ b/src/compiler/crystal/codegen/fun.cr @@ -101,9 +101,13 @@ class Crystal::CodeGenVisitor context.fun.add_attribute LLVM::Attribute::UWTable, value: @program.has_flag?("aarch64") ? LLVM::UWTableKind::Sync : LLVM::UWTableKind::Async {% end %} - if @program.has_flag?("darwin") + if @frame_pointers.always? + context.fun.add_attribute "frame-pointer", value: "all" + elsif @program.has_flag?("darwin") # Disable frame pointer elimination in Darwin, as it causes issues during stack unwind context.fun.add_target_dependent_attribute "frame-pointer", "all" + elsif @frame_pointers.non_leaf? + context.fun.add_attribute "frame-pointer", value: "non-leaf" end new_entry_block diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 2c5492445e0b..e6872211d683 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -397,6 +397,14 @@ class Crystal::Command opts.on("--no-debug", "Skip any symbolic debug info") do compiler.debug = Crystal::Debug::None end + + opts.on("--frame-pointers auto|always|non-leaf", "Control the preservation of frame pointers") do |value| + if frame_pointers = FramePointers.parse?(value) + compiler.frame_pointers = frame_pointers + else + error "Invalid value `#{value}` for frame-pointers" + end + end end opts.on("-D FLAG", "--define FLAG", "Define a compile-time flag") do |flag| diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 237783c2ed8c..215a60f96096 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -16,6 +16,12 @@ module Crystal Default = LineNumbers end + enum FramePointers + Auto + Always + NonLeaf + end + # Main interface to the compiler. # # A Compiler parses source code, type checks it and @@ -45,6 +51,9 @@ module Crystal # code by the `flag?(...)` macro method. property flags = [] of String + # Controls generation of frame pointers. + property frame_pointers = FramePointers::Auto + # If `true`, the executable will be generated with debug code # that can be understood by `gdb` and `lldb`. property debug = Debug::Default @@ -297,7 +306,8 @@ module Crystal private def codegen(program, node : ASTNode, sources, output_filename) llvm_modules = @progress_tracker.stage("Codegen (crystal)") do - program.codegen node, debug: debug, single_module: @single_module || @cross_compile || !@emit_targets.none? + program.codegen node, debug: debug, frame_pointers: frame_pointers, + single_module: @single_module || @cross_compile || !@emit_targets.none? end output_dir = CacheDir.instance.directory_for(sources) diff --git a/src/llvm/function.cr b/src/llvm/function.cr index a586cd6fdde5..d643e74d758b 100644 --- a/src/llvm/function.cr +++ b/src/llvm/function.cr @@ -28,6 +28,13 @@ struct LLVM::Function end end + def add_attribute(attribute : String, index = AttributeIndex::FunctionIndex, *, value : String) + context = LibLLVM.get_module_context(LibLLVM.get_global_parent(self)) + attribute_ref = LibLLVM.create_string_attribute(context, attribute, attribute.bytesize, + value, value.bytesize) + LibLLVM.add_attribute_at_index(self, index, attribute_ref) + end + def add_attribute(attribute : Attribute, index = AttributeIndex::FunctionIndex, *, value) return if attribute.value == 0 diff --git a/src/llvm/lib_llvm/core.cr b/src/llvm/lib_llvm/core.cr index 1d44cb005d98..cc706bbc051d 100644 --- a/src/llvm/lib_llvm/core.cr +++ b/src/llvm/lib_llvm/core.cr @@ -25,6 +25,7 @@ lib LibLLVM fun get_enum_attribute_kind_for_name = LLVMGetEnumAttributeKindForName(name : Char*, s_len : SizeT) : UInt fun get_last_enum_attribute_kind = LLVMGetLastEnumAttributeKind : UInt fun create_enum_attribute = LLVMCreateEnumAttribute(c : ContextRef, kind_id : UInt, val : UInt64) : AttributeRef + fun create_string_attribute = LLVMCreateStringAttribute(c : ContextRef, k : Char*, k_length : UInt, v : Char*, v_length : UInt) : AttributeRef {% unless LibLLVM::IS_LT_120 %} fun create_type_attribute = LLVMCreateTypeAttribute(c : ContextRef, kind_id : UInt, type_ref : TypeRef) : AttributeRef {% end %}