Skip to content

Commit

Permalink
Set linkage of __crystal_* funs to internal (#15439)
Browse files Browse the repository at this point in the history
Unlike the usual `fun` for which we might expect to have external linkage,
the `__crystal_*` funs are an internal implementation detail to abstract calls
to stdlib from the codegen pass (e.g. `__crystal_once`, `__crystal_raise`, ...).

When compiling to a single module, we can mark them to have internal
linkage, so they're only considered inside the object file. We already do
that for all globals and `def`.
  • Loading branch information
ysbaddaden authored Feb 11, 2025
1 parent cb7782d commit ade1e6f
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 2 deletions.
21 changes: 21 additions & 0 deletions spec/compiler/codegen/fun_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require "../../spec_helper"

describe "Codegen: fun" do
it "sets external linkage by default" do
mod = codegen(<<-CRYSTAL, inject_primitives: false, single_module: false)
fun foo; end
fun __crystal_foo; end
CRYSTAL
mod.functions["foo"].linkage.should eq(LLVM::Linkage::External)
mod.functions["__crystal_foo"].linkage.should eq(LLVM::Linkage::External)
end

it "sets internal linkage to __crystal_ funs when compiling to single module" do
mod = codegen(<<-CRYSTAL, inject_primitives: false, single_module: true)
fun foo; end
fun __crystal_foo; end
CRYSTAL
mod.functions["foo"].linkage.should eq(LLVM::Linkage::External)
mod.functions["__crystal_foo"].linkage.should eq(LLVM::Linkage::Internal)
end
end
4 changes: 2 additions & 2 deletions spec/spec_helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,9 @@ def prepare_macro_call(macro_body, flags = nil, &)
{program, a_macro, call}
end

def codegen(code, inject_primitives = true, debug = Crystal::Debug::None, filename = __FILE__)
def codegen(code, *, inject_primitives = true, single_module = false, debug = Crystal::Debug::None, filename = __FILE__)
result = semantic code, inject_primitives: inject_primitives, filename: filename
result.program.codegen(result.node, single_module: false, debug: debug)[""].mod
result.program.codegen(result.node, single_module: single_module, debug: debug)[""].mod
end

private def new_program
Expand Down
1 change: 1 addition & 0 deletions src/compiler/crystal/codegen/codegen.cr
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ module Crystal
ret_type = @llvm_typer.llvm_return_type(@main_ret_type)
main_type = LLVM::Type.function([llvm_context.int32, llvm_context.void_pointer.pointer], ret_type)
@main = @llvm_mod.functions.add(MAIN_NAME, main_type)
@main.linkage = LLVM::Linkage::Internal if @single_module
@fun_types = { {@llvm_mod, MAIN_NAME} => main_type }

if @program.has_flag?("msvc")
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/crystal/codegen/fun.cr
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,14 @@ class Crystal::CodeGenVisitor
context.fun.call_convention = call_convention
end

if @single_module && mangled_name.starts_with?("__crystal_")
# FIXME: macos ld fails to link when the personality fun is internal; it
# might work with lld so we might want to check the linker?
unless @program.has_flag?("darwin") && mangled_name.starts_with?("__crystal_personality")
context.fun.linkage = LLVM::Linkage::Internal
end
end

i = 0
args.each do |arg|
param = context.fun.params[i + offset]
Expand Down

0 comments on commit ade1e6f

Please sign in to comment.