@@ -218,6 +218,10 @@ bitfield public LlvmJitFlags {
218218 gen_exe ,
219219 // Is debug info needed. Debug info not fully supported and not tested yet.
220220 need_di ,
221+ // Offline AOT object linked into a host that already has the runtime. Like gen_exe,
222+ // codegen-process addresses are meaningless, so builtin-accessor globals are emitted
223+ // internal + null and re-resolved by a load ctor (host owns the modules/functions).
224+ emit_aot_object ,
221225}
222226
223227// Non-string workhorse key types that get the inline open-addressed find/at (build_workhorse_*):
@@ -1512,32 +1516,35 @@ class public LlvmJitVisitor : AstVisitor {
15121516 var global_addr = LLVMGetNamedGlobal (g_mod , name .glob ())
15131517 if (global_addr == null ) {
15141518 global_addr = LLVMAddGlobal (g_mod , nonnull_type , name .glob ())
1515- if (flags .gen_exe ) {
1519+ if (flags .gen_exe || flags . emit_aot_object ) {
15161520 // nolint:STYLE014
1517- // For standalone exes the JIT-process address is meaningless
1518- // (different ASLR slide in the new process). Leave the global
1519- // null at link time; the runtime initialize_modules() walks
1520- // every used builtin and overwrites this slot via
1521- // jit_initialize_extern_function. Baking the codegen-time
1522- // address as a constant initializer would let LLVM
1523- // constant-fold loads — observed at top-level `let` initializers
1524- // — to the JIT-process immediate, which the standalone exe then
1525- // calls and crashes on (dyld loaded the dylib elsewhere).
1521+ // The JIT-process address is meaningless in the host (different ASLR slide),
1522+ // and baking it as a const initializer lets LLVM constant-fold loads to that
1523+ // dead immediate. Leave null + private; a load-time init (initialize_modules
1524+ // for exe, the globinit ctor for the object) fills it via the runtime accessor.
15261525 set_private_linkage (global_addr )
15271526 LLVMSetInitializer (global_addr , LLVMConstPointerNull (nonnull_type ))
15281527 } else {
15291528 set_public_linkage (global_addr )
15301529 LLVMSetInitializer (global_addr , types .ConstPtr (address | > intptr , nonnull_type ))
15311530 }
1532- } elif (! flags .gen_exe ) {
1533- // JIT mode codegen's each function once, so a duplicate name is a real bug. Exe mode
1534- // re-codegens the same global-var initializers in init_globals + init_globals_clone, so the
1535- // same slot is legitimately re-encountered — reuse silently (idempotent: same name, same slot).
1531+ } elif (! (flags .gen_exe || flags .emit_aot_object )) {
1532+ // JIT codegens each function once, so a duplicate is a real bug (exe/object modes
1533+ // legitimately re-encounter a slot via init_globals + init_globals_clone).
15361534 failed ("Name duplicate { name .glob ()} \n " )
15371535 }
15381536 return load_local_const (global_addr , nonnull_type )
15391537 }
15401538
1539+ // emit-object mode: record a cross-module call/ctor target glob (just created
1540+ // null by save_address_global) so emit_aot_object_globinit fills it with the
1541+ // host Context's SimFunction* (das_aot_get_func_by_mnh) at load.
1542+ def aot_record_func_glob (name : DllName ; func : Function ?) {
1543+ if (g_emit_aot_object_globals ) {
1544+ g_aot_func_globs | > push ((glob = LLVMGetNamedGlobal (g_mod , name .glob ()), mnh = func .getMangledNameHash ))
1545+ }
1546+ }
1547+
15411548 def set_meta_cconv (instr : LLVMOpaqueValue ?; conv : string ) {
15421549 var values : LLVMOpaqueValue ? [1 ]
15431550 values [0] = LLVMMDStringInContext (g_ctx , conv , uint (length (conv )))
@@ -1712,6 +1719,7 @@ class public LlvmJitVisitor : AstVisitor {
17121719 if (glob_fn_ptr == null ) {
17131720 var MNH_ADDR = get_function_addr (jit_context , expr .func )
17141721 glob_fn_ptr = save_address_global (uid .get_dll_fn_name_ptr (expr .func ), MNH_ADDR )
1722+ aot_record_func_glob (uid .get_dll_fn_name_ptr (expr .func ), expr .func )
17151723 }
17161724 var params : array < LLVMOpaqueValue ?> // nolint:STYLE012 — conditional cmresEval push between fixed args
17171725 params | > push (glob_fn_ptr ) // mnh
@@ -4252,6 +4260,7 @@ class public LlvmJitVisitor : AstVisitor {
42524260 if (ctor == null ) {
42534261 var MNH_ADDR = get_function_addr (jit_context , expr_func )
42544262 ctor = save_address_global (name , MNH_ADDR )
4263+ aot_record_func_glob (name , expr_func )
42554264 }
42564265 var params < - [
42574266 ctor , // mnh
@@ -7915,16 +7924,11 @@ def public generate_llvm_code(var dll : DLLHandle?; fmna : DllName; dll_enabled
79157924 return code
79167925}
79177926
7918- // Creates a single global constructor (run at compiled-object startup) for complex global types
7919- // like fileInfo that can't be const-initialized.
7927+ // Emit the module's load/unload ctors into llvm.global_ctors/dtors: the FileInfo ctor/dtor,
7928+ // plus -- in offline AOT object mode (emit_aot) -- a ctor calling das_aot_register per function
7929+ // so the .o self-registers into the AOT library at load. funcs/uids/prog read only when emit_aot.
79207930[macro_function ]
7921- // Emit the module's load/unload constructors into llvm.global_ctors/dtors: the
7922- // FileInfo ctor/dtor, plus -- in offline AOT object mode (emit_aot) -- a ctor
7923- // that calls the linker-resolved extern das_aot_register(aotHash, publ) for each
7924- // of this module's functions, so the emitted .o self-registers into the AOT
7925- // library at load (reusing the existing AotFactory / linkCppAot path via
7926- // SimNode_Jit). funcs/uids/prog are only read when emit_aot is true.
7927- def public generate_global_ctors_dtors (types : PrimitiveTypes ?; jit_mode : bool ; emit_aot : bool ; funcs : array < FunctionPtr > ; var uids : UidNodes ?; prog : Program ?) {
7931+ def public generate_global_ctors_dtors (types : PrimitiveTypes ?; jit_mode : bool ; emit_aot : bool ; funcs : array < FunctionPtr > ; var uids : UidNodes ?; prog : Program ?; glob_ctor : LLVMOpaqueValue ? = null ) {
79287932 var ctor_dtor < - [
79297933 // FileInfo
79307934 generate_fileinfo_ctor_dtor (* types , active_filenames , jit_mode )
@@ -7938,8 +7942,7 @@ def public generate_global_ctors_dtors(types : PrimitiveTypes?; jit_mode : bool;
79387942 regFn = LLVMAddFunctionWithType (g_mod , "das_aot_register" , regType )
79397943 }
79407944 let functionType = LLVMFunctionType (types .t_void , null , 0u , 0 )
7941- // Private: referenced only via llvm.global_ctors, and each object defines it,
7942- // so public linkage would clash when many objects link into one host.
7945+ // Private: each object defines it, so public linkage would clash when many link together.
79437946 let reg_ctor = LLVMAddFunctionWithType (g_mod , "das_aot_register_constructor" , functionType )
79447947 let reg_dtor = LLVMAddFunctionWithType (g_mod , "das_aot_register_destructor" , functionType )
79457948 set_private_linkage (reg_ctor )
@@ -7961,6 +7964,19 @@ def public generate_global_ctors_dtors(types : PrimitiveTypes?; jit_mode : bool;
79617964 var reg_args = array < LLVMValueRef > (types .ConstI64 (aot_hash ), publ_ptr )
79627965 LLVMBuildCall2 (ctor_builder , regType , regFn , reg_args , "" )
79637966 }
7967+ // Record this object's glob re-resolution callback; the runtime runs it lazily at
7968+ // the first AotLibrary drain (NOT here at static-init, where its accessors would
7969+ // fault on a null bound environment).
7970+ if (glob_ctor ! = null ) {
7971+ let giType = LLVMFunctionType (types .t_void , fixed_array < LLVMTypeRef > (types .LLVMVoidPtrType ()))
7972+ var giFn = LLVMGetNamedFunction (g_mod , "das_aot_register_globinit" )
7973+ if (giFn == null ) {
7974+ giFn = LLVMAddFunctionWithType (g_mod , "das_aot_register_globinit" , giType )
7975+ }
7976+ var gi_ptr = LLVMBuildPointerCast (ctor_builder , glob_ctor , types .LLVMVoidPtrType (), "" )
7977+ var gi_args = array < LLVMValueRef > (gi_ptr )
7978+ LLVMBuildCall2 (ctor_builder , giType , giFn , gi_args , "" )
7979+ }
79647980 LLVMBuildRetVoid (ctor_builder )
79657981 LLVMBuildRetVoid (dtor_builder )
79667982 LLVMDisposeBuilder (ctor_builder )
0 commit comments