diff --git a/daslib/aot_cpp.das b/daslib/aot_cpp.das index 0db170c1b0..512f1e91c2 100644 --- a/daslib/aot_cpp.das +++ b/daslib/aot_cpp.das @@ -569,6 +569,10 @@ class public NoAotMarker : AstVisitor { } def override preVisitFunction(var f : FunctionPtr) { func = f; + if (func.moreFlags2.needCallerStackFrame && !func.flags.noAot) { + func.flags.noAot = true + to_log(LOG_INFO, "AOT: '{func.name}' can reach heap_collect - keeping interpreter mode\n") + } } def override visitFunction(var that : FunctionPtr) : FunctionPtr { var tmp := func diff --git a/modules/dasLLVM/daslib/llvm_exe.das b/modules/dasLLVM/daslib/llvm_exe.das index 45632be773..aa871e69e4 100644 --- a/modules/dasLLVM/daslib/llvm_exe.das +++ b/modules/dasLLVM/daslib/llvm_exe.das @@ -1056,6 +1056,10 @@ def public collect_standalone_functions(prog : Program?; strict : bool) : Standa has_no_jit = true to_log(LOG_ERROR, "LLVM EXE: function '{fun.name}' is marked no_jit but standalone exe requires all functions to be JIT-compiled\n") } + if (!fun.flags.builtIn && fun.moreFlags2.needCallerStackFrame) { + has_no_jit = true + to_log(LOG_ERROR, "LLVM EXE: function '{fun.name}' can reach heap_collect, whose locals a compiled frame does not expose as GC roots; there is no interpreter here to fall back to\n") + } if (fun.moreFlags.pinvoke) { any_pinvoke = true } diff --git a/modules/dasLLVM/daslib/llvm_jit_plan.das b/modules/dasLLVM/daslib/llvm_jit_plan.das index b2fff9aad6..a34ce217b0 100644 --- a/modules/dasLLVM/daslib/llvm_jit_plan.das +++ b/modules/dasLLVM/daslib/llvm_jit_plan.das @@ -902,6 +902,7 @@ def public make_jit_plan(prog : Program?; var ctx : Context?; announce : bool) : plan.use_host_cpu = empty(plan.baseline) && (plan.emit_aot_object ? true : (!plan.gen_exe || plan.exe_host_cpu)) plan.log_jit_time = prog._options |> find_arg("log_compile_time") ?as tBool ?? prog.policies.log_compile_time + var gc_carriers = 0 prog |> for_each_module() $(mod) { mod |> for_each_function("") $(fun) { // In object mode emit the whole used, non-noAot set (all modules + @@ -910,7 +911,14 @@ def public make_jit_plan(prog : Program?; var ctx : Context?; announce : bool) : if (is_used(prog, fun) && (!plan.emit_aot_object || !fun.flags.noAot) && get_function_by_mangled_name_hash(hash(get_mangled_name(fun)), *ctx) != null && jit_selects(fun, fun.moreFlags.requestJit, plan.aot_host, plan.jit_all_functions, ctx)) { - if (!fun.moreFlags.requestNoJit) { + let gc_carrier = fun.moreFlags2.needCallerStackFrame && !plan.gen_exe && !plan.gen_lib + if (gc_carrier && !fun.moreFlags.requestNoJit) { + gc_carriers++ + if (announce) { + to_log(LOG_INFO, "LLVM JIT: '{fun.name}' can reach heap_collect - keeping interpreter mode\n") + } + } + if (!fun.moreFlags.requestNoJit && !gc_carrier) { plan.candidates |> emplace(fun) } else { plan.disabled |> emplace(fun) @@ -918,6 +926,10 @@ def public make_jit_plan(prog : Program?; var ctx : Context?; announce : bool) : } } } + if (announce && gc_carriers != 0) { + to_log(LOG_WARNING, "LLVM JIT: {gc_carriers} function(s) reach heap_collect and keep interpreter mode - " + + "a compiled frame's locals are not GC roots, so a collect under one would sweep them while live\n") + } // Content-address the DLL: distinct (AST + codegen version + opt flags) → distinct files, same // inputs → same filename → cache hit. Only in dll-mode with the default output path; a pinned // jit_output_path or an exe keeps the user's path.