Skip to content

Commit

Permalink
Add --frame-pointers to control the preservation of frame pointers
Browse files Browse the repository at this point in the history
Frame pointers are extremely useful for quickly generating accurate
stack traces when debugging and profiling release builds. In particular,
it allows the use of Linux's perf stack, which offers high-performance
profiling but only supports unwinding via frame pointers, barring some
extremely slow workarounds:

https://blogs.gnome.org/chergert/2022/12/31/frame-pointers-and-other-practical-near-term-solutions/
  • Loading branch information
refi64 committed Oct 4, 2023
1 parent 9574750 commit 74851ec
Show file tree
Hide file tree
Showing 7 changed files with 44 additions and 5 deletions.
2 changes: 2 additions & 0 deletions man/crystal.1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions src/compiler/crystal/codegen/codegen.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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, frame_pointers = FramePointers::Auto,
debug = Debug::Default)
visitor = CodeGenVisitor.new self, node, single_module: single_module,
frame_pointers: frame_pointers, debug: debug
visitor.accept node
visitor.process_finished_hooks
visitor.finish
Expand Down Expand Up @@ -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,
@frame_pointers : FramePointers = FramePointers::Auto,
@debug = Debug::Default)
@abi = @program.target_machine.abi
@llvm_context = LLVM::Context.new
# LLVM::Context.register(@llvm_context, "main")
Expand Down
6 changes: 5 additions & 1 deletion src/compiler/crystal/codegen/fun.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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 == FramePointers::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 == FramePointers::NonLeaf
context.fun.add_attribute "frame-pointer", value: "non-leaf"
end

new_entry_block
Expand Down
10 changes: 10 additions & 0 deletions src/compiler/crystal/command.cr
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,16 @@ class Crystal::Command
end
end

unless no_codegen
opts.on("--frame-pointers auto|always|non-leaf", "Control the preservation of frame pointers") do |frame_pointers_s|
if frame_pointers = FramePointers.parse? frame_pointers_s
compiler.frame_pointers = frame_pointers
else
error "Invalid value `#{frame_pointers_s}` for frame-pointers"
end
end
end

opts.on("--error-trace", "Show full error trace") do
compiler.show_error_trace = true
@error_trace = true
Expand Down
12 changes: 11 additions & 1 deletion src/compiler/crystal/compiler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -266,7 +275,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 || @release || @cross_compile || !@emit_targets.none?
program.codegen node, debug: debug, frame_pointers: frame_pointers,
single_module: @single_module || @release || @cross_compile || !@emit_targets.none?
end

output_dir = CacheDir.instance.directory_for(sources)
Expand Down
7 changes: 7 additions & 0 deletions src/llvm/function.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions src/llvm/lib_llvm.cr
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ lib LibLLVM
fun get_last_enum_attribute_kind = LLVMGetLastEnumAttributeKind : UInt
fun get_enum_attribute_kind_for_name = LLVMGetEnumAttributeKindForName(name : Char*, s_len : LibC::SizeT) : 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 : UInt32, v : Char*, v_length : UInt32) : AttributeRef
fun add_attribute_at_index = LLVMAddAttributeAtIndex(f : ValueRef, idx : AttributeIndex, a : AttributeRef)
fun get_enum_attribute_at_index = LLVMGetEnumAttributeAtIndex(f : ValueRef, idx : AttributeIndex, kind_id : UInt) : AttributeRef
fun add_call_site_attribute = LLVMAddCallSiteAttribute(f : ValueRef, idx : AttributeIndex, value : AttributeRef)
Expand Down

0 comments on commit 74851ec

Please sign in to comment.