From 52907c1a3fd2e5f0ef73c7260bebd0bec6b4867c Mon Sep 17 00:00:00 2001 From: Churkin Aleksey Date: Fri, 28 Aug 2026 12:13:40 +0300 Subject: [PATCH 1/4] tests: cover a handle whose index yields a pointer Every annotation in tree yields a ref from makeIndexType, so nothing reached the jit's at`handle path for a pointer result - the shape an embedder's keyed container has. dasUnitTest gains PtrSlots, indexed by string, yielding `int?`. Co-Authored-By: Claude Opus 5 --- modules/dasUnitTest/test_handles.cpp | 84 ++++++++++++++++++++++++++++ tests/jit_tests/handle_ptr_index.das | 28 ++++++++++ 2 files changed, 112 insertions(+) create mode 100644 tests/jit_tests/handle_ptr_index.das diff --git a/modules/dasUnitTest/test_handles.cpp b/modules/dasUnitTest/test_handles.cpp index 92650a77cf..4ad96a3e60 100644 --- a/modules/dasUnitTest/test_handles.cpp +++ b/modules/dasUnitTest/test_handles.cpp @@ -370,6 +370,86 @@ bool tempArrayExample( const TArray & arr, return (arr.size == 1) && (strcmp(arr[0], "one") == 0); } +// A keyed handle whose index yields a POINTER, and null for a missing key - the shape an +// embedder's keyed container has (an ecs Object, a json object). Every annotation that overrides +// makeIndexType in tree yields a ref, so without this one nothing can reach the jit's pointer arm: +// a jit that loads through the returned pointer hands back the pointee instead, and dereferences +// null on a miss. +struct PtrSlots { + int a = 7; + int b = 9; +}; + +MAKE_TYPE_FACTORY(PtrSlots,PtrSlots); + +// the at`handle contract: ( handle, index natively typed, Context *, LineInfoArg * ) +static char * ptr_slots_at ( void * pSlots, char * key, Context *, LineInfoArg * ) { + auto * slots = (PtrSlots *) pSlots; + if ( !slots || !key ) return nullptr; + if ( key[0]=='a' && key[1]==0 ) return (char *) &slots->a; + if ( key[0]=='b' && key[1]==0 ) return (char *) &slots->b; + return nullptr; +} + +struct PtrSlotsAnnotation final : ManagedStructureAnnotation { + struct SimNode_PtrSlotsAt : SimNode_At { + DAS_PTR_NODE; + SimNode_PtrSlotsAt ( const LineInfo & at, SimNode * rv, SimNode * idx, uint32_t ofs ) + : SimNode_At(at, rv, idx, 0, ofs, 0, "PtrSlots[key]") {} + __forceinline char * compute ( Context & context ) { + auto * slots = value->evalPtr(context); + char * key = cast::to(index->eval(context)); + char * res = ptr_slots_at(slots, key, &context, nullptr); + return res ? res + offset : nullptr; + } + virtual SimNode * visit ( SimVisitor & vis ) override { + using TT = PtrSlots; + V_BEGIN(); + V_OP_TT(AtPtrSlots); + V_SUB(value); + V_SUB(index); + V_END(); + } + }; + PtrSlotsAnnotation ( ModuleLibrary & ml ) : ManagedStructureAnnotation("PtrSlots", ml, "PtrSlots") { + addField("a"); + addField("b"); + atType = makeType(ml); + } + virtual bool isIndexable ( const TypeDeclPtr & indexType ) const override { + return indexType->isSimpleType(Type::tString); + } + virtual TypeDeclPtr makeIndexType ( ExpressionPtr, ExpressionPtr ) const override { + return new TypeDecl(*atType); + } + virtual SimNode * simulateGetAt ( Context & context, const LineInfo & at, const TypeDeclPtr &, + ExpressionPtr rv, ExpressionPtr idx, uint32_t ofs ) const override { + return context.code->makeNode(at, simulateExpression(context, rv), + simulateExpression(context, idx), ofs); + } + virtual void * jitGetAt ( Type indexType ) const override { + // one index type only: the key. The jit passes it natively typed, same as the ref-yielding + // annotations do for their int indices. + return indexType==Type::tString ? (void *) &ptr_slots_at : nullptr; + } + virtual void gc_collect ( gc_root * target, gc_root * from ) override { + ManagedStructureAnnotation::gc_collect(target, from); + if ( atType ) atType->gc_collect(target, from); + } + virtual void visitTypeDecls ( const function & callback ) override { + ManagedStructureAnnotation::visitTypeDecls(callback); + if ( atType ) callback(atType); + } + TypeDeclPtr atType = nullptr; +}; + +void testPtrSlots(const TBlock & blk, Context * context, LineInfoArg * at) { + PtrSlots slots; + vec4f args[1]; + args[0] = cast::from(&slots); + context->invoke(blk, args, nullptr, at); +} + void testPoint3Array(const TBlock & blk, Context * context, LineInfoArg * at) { Point3Array arr; for (int32_t x = 0; x != 10; ++x) { @@ -625,6 +705,10 @@ Module_UnitTest::Module_UnitTest() : Module("UnitTest") { SideEffects::none, "testStringArgLength")->arg("str"); addExtern(*this, lib, "testPoint3Array", SideEffects::modifyExternal, "testPoint3Array"); + // keyed handle whose index yields a pointer - the jit's pointer arm (see PtrSlotsAnnotation) + addAnnotation(new PtrSlotsAnnotation(lib)); + addExtern(*this, lib, "testPtrSlots", + SideEffects::modifyExternal, "testPtrSlots"); addExtern(*this, lib, "testNotLocalObject", SideEffects::modifyExternal, "testNotLocalObject"); addExtern(*this, lib, "testCMRES", diff --git a/tests/jit_tests/handle_ptr_index.das b/tests/jit_tests/handle_ptr_index.das new file mode 100644 index 0000000000..0bd0281578 --- /dev/null +++ b/tests/jit_tests/handle_ptr_index.das @@ -0,0 +1,28 @@ +options gen2 +require dastest/testing_boost + +require UnitTest + +//! A keyed handle (UnitTest::PtrSlots) indexes by string and yields `int?`, null for a missing key. +//! The jit's at`handle hook returns that pointer as the value of the expression: loading through it +//! hands back the pointee instead, and dereferences null on a miss. Every other annotation in tree +//! yields a ref from makeIndexType, so this is the only test that reaches the pointer arm. + +def check_slots(t : T?; var slots : PtrSlots) { + t |> success(!jit_enabled() || is_jit_function(@@ < (t : T?; var slots : PtrSlots) : void > check_slots)) + let pa = slots["a"] + let pb = slots["b"] + t |> success(pa != null) + t |> success(pb != null) + t |> equal(7, *pa) + t |> equal(9, *pb) + // a missing key is a null pointer, and evaluating the expression must not read through it + t |> success(slots["zz"] == null) +} + +[test] +def test_handle_ptr_index(t : T?) { + testPtrSlots() $(var slots : PtrSlots) { + check_slots(t, slots) + } +} From a924e6b97d1027e77d68ecc811688d1d050bff3e Mon Sep 17 00:00:00 2001 From: Churkin Aleksey Date: Fri, 28 Aug 2026 15:07:19 +0300 Subject: [PATCH 2/4] jit: catch (and fix) address globals an AOT object never resolves An address global nothing fills at load is not merely unresolved: LLVM folds its loads to null, proves the body UB and drops it to a zero-length `unreachable` that shares an address with the next function, so das_aot_register binds that hash to alien code. collect_unresolved_address_globals reports them and run_jit panics before the optimizer erases the evidence. It found one live case: ascend-new of a handled type, which the emitter names after the ascend node while CollectExternVisitor visited only ExprNew. Co-Authored-By: Claude Opus 5 --- modules/dasLLVM/daslib/llvm_aot.das | 34 +++++++++++++++++++++++++ modules/dasLLVM/daslib/llvm_exe.das | 12 +++++++++ modules/dasLLVM/daslib/llvm_jit_run.das | 8 ++++++ 3 files changed, 54 insertions(+) diff --git a/modules/dasLLVM/daslib/llvm_aot.das b/modules/dasLLVM/daslib/llvm_aot.das index e5e3370f5a..cef9d682b3 100644 --- a/modules/dasLLVM/daslib/llvm_aot.das +++ b/modules/dasLLVM/daslib/llvm_aot.das @@ -10,6 +10,8 @@ require llvm/daslib/llvm_exe require llvm/daslib/llvm_dll_utils require daslib/ast_boost require daslib/rtti +require daslib/safe_addr +require strings //! LLVM-AOT registration pass. After the JIT visitor has generated + optimized the module (as for a //! normal exe), this adds the offline-AOT-object registration layer so a statically-linked .o binds @@ -101,3 +103,35 @@ def public build_llvm_aot_ctors(ctx : LLVMContextRef; mod : LLVMOpaqueModule?; v let glob_ctor = emit_aot_object_globinit(ctx, mod, types, funcs, uids) return emit_aot_register_ctor_dtor(types, funcs, uids, prog, glob_ctor) } + +//! Names the DllName.glob() address globals nothing fills at load, for the caller to panic on. +def public collect_unresolved_address_globals(mod : LLVMOpaqueModule?) : array { + var bad : array + var glob = LLVMGetFirstGlobal(mod) + while (glob != null) { + let init = LLVMGetInitializer(glob) + if (init != null && LLVMIsNull(init) != 0) { + var name_len = 0ul + let name = LLVMGetValueName2(glob, safe_addr(name_len)) + if (name |> ends_with(" glob")) { + var loaded = false + var escaped = false + var use = LLVMGetFirstUse(glob) + while (use != null) { + let user = LLVMGetUser(use) + if (LLVMIsALoadInst(user) != null) { + loaded = true + } else { + escaped = true // stored into, or the address itself is passed on + } + use = LLVMGetNextUse(use) + } + if (loaded && !escaped) { + bad |> push(clone_string(name)) + } + } + } + glob = LLVMGetNextGlobal(glob) + } + return <- bad +} diff --git a/modules/dasLLVM/daslib/llvm_exe.das b/modules/dasLLVM/daslib/llvm_exe.das index c5f910af45..1dc887ec8e 100644 --- a/modules/dasLLVM/daslib/llvm_exe.das +++ b/modules/dasLLVM/daslib/llvm_exe.das @@ -413,6 +413,18 @@ class public CollectExternVisitor : AstVisitor { } } + // `new [[Handle...]]` allocates through the annotation the same way ExprNew does, and the + // emitter names that slot after the ascend node - so it needs its own resolution. + def override preVisitExprAscend(expr : ExprAscend?) { + assume subT = expr.subexpr._type + if (subT.isHandle) { + let name = uid.get_ascend_new(expr) + if (initialize(name)) { + register_new_from_annotation(name, subT.annotation) + } + } + } + def override preVisitExprNew(expr : ExprNew?) { if (expr.typeexpr.isHandle) { let name = uid.get_new(expr.typeexpr) diff --git a/modules/dasLLVM/daslib/llvm_jit_run.das b/modules/dasLLVM/daslib/llvm_jit_run.das index 84217eebd1..efdbcf9153 100644 --- a/modules/dasLLVM/daslib/llvm_jit_run.das +++ b/modules/dasLLVM/daslib/llvm_jit_run.das @@ -971,6 +971,14 @@ def public run_jit(prog : Program?; var ctx : Context?) : bool { // nolint:STYL aot_dtor = pair._1 } generate_global_ctors_dtors(g_prim_t, emit_aot_object ? false : !(use_dll || gen_exe), aot_ctor, aot_dtor) + if (emit_aot_object) { + // before optimization: the folding this catches also deletes the evidence + let unresolved = collect_unresolved_address_globals(g_mod) + if (!(unresolved |> empty())) { + reset_jit_globals_after_failure() + panic("Internal jit error. AOT address globals left unresolved by the globinit ctor: {join(unresolved, ", ")}\n") + } + } if (g_jit_fast_math) { apply_fast_math_to_module(g_mod) // EXPERIMENT: stamp all FP ops fast (non-bit-exact ceiling) } From c94ec7f5414ef1cfc1d7d9d21a2e3371cbaf27bb Mon Sep 17 00:00:00 2001 From: Churkin Aleksey Date: Sat, 29 Aug 2026 20:45:33 +0300 Subject: [PATCH 3/4] jit: 32-bit % is an srem, not a double division The 32-bit integer remainder was lowered as a sitofp, a double divide, an fptosi, a multiply and a subtract, where 8/16/64-bit already emitted a plain srem/urem - a ~20 cycle divide in place of the multiply-and-shift LLVM picks whenever the divisor is a constant. The float form was there to dodge the two cases native srem gets wrong, but the emitter already handles both before it reaches this point: check_divide_by_0 raises the das error for rhs == 0, and the % arm rewrites rhs to 1 when lhs == INT_MIN && rhs == -1. The guards that made the float path safe make the native one safe too. Measured on an enlisted act-stage ES dominated by `x % 4093`, mean of 3 runs: interpreter 283 us/tick, jit float remainder 131, jit native srem 53, C++ AOT 45 - the jit goes from 2.9x the AOT backend to 1.2x on that ES. The DLL cache key now also folds the Context offsets the emitter bakes into every jitted function (stopFlags, evalTop). It covered only globals and shared, so a future Context layout change would have served a stale DLL. Co-Authored-By: Claude Opus 5 --- modules/dasLLVM/daslib/llvm_boost.das | 21 --------------------- modules/dasLLVM/daslib/llvm_jit.das | 24 ++++-------------------- modules/dasLLVM/daslib/llvm_jit_run.das | 4 +++- 3 files changed, 7 insertions(+), 42 deletions(-) diff --git a/modules/dasLLVM/daslib/llvm_boost.das b/modules/dasLLVM/daslib/llvm_boost.das index a9f98d4d36..f865318f94 100644 --- a/modules/dasLLVM/daslib/llvm_boost.das +++ b/modules/dasLLVM/daslib/llvm_boost.das @@ -183,27 +183,6 @@ def StructType(types : PrimitiveTypes?; var fields : array) { } -def LLVMBuildSRemInt32(builder : LLVMOpaqueBuilder?; var types : PrimitiveTypes?; lhs, rhs : LLVMOpaqueValue?; name : string) { - // convert lhs % rhs to lhs - int(double(lhs)/double(rhs))*rhs - let l = LLVMBuildSIToFP(builder, lhs, types.t_double, "") - let r = LLVMBuildSIToFP(builder, rhs, types.t_double, "") - let d = LLVMBuildFDiv(builder, l, r, "") - let i = LLVMBuildFPToSI(builder, d, types.t_int32, "") - let m = LLVMBuildMul(builder, i, rhs, "") - return LLVMBuildSub(builder, lhs, m, name) -} - - -def LLVMBuildURemUInt32(builder : LLVMOpaqueBuilder?; var types : PrimitiveTypes?; lhs, rhs : LLVMOpaqueValue?; name : string) { - // convert lhs % rhs to lhs - uint(double(lhs)/double(rhs))*rhs - let l = LLVMBuildUIToFP(builder, lhs, types.t_double, "") - let r = LLVMBuildUIToFP(builder, rhs, types.t_double, "") - let d = LLVMBuildFDiv(builder, l, r, "") - let i = LLVMBuildFPToUI(builder, d, types.t_int32, "") - let m = LLVMBuildMul(builder, i, rhs, "") - return LLVMBuildSub(builder, lhs, m, name) -} - def LLVMIsVector3(typ : LLVMOpaqueType?) : bool { return (LLVMGetTypeKind(typ) == LLVMTypeKind.LLVMVectorTypeKind) && (LLVMGetVectorSize(typ) == 3u) } diff --git a/modules/dasLLVM/daslib/llvm_jit.das b/modules/dasLLVM/daslib/llvm_jit.das index 35c52ba9c5..8d37ad1e00 100644 --- a/modules/dasLLVM/daslib/llvm_jit.das +++ b/modules/dasLLVM/daslib/llvm_jit.das @@ -3256,17 +3256,9 @@ class public LlvmJitVisitor : AstVisitor { } } elif (expr.op == "%") { if (opType.isSignedInteger || (opType.isVectorType && opType.vectorBaseType == Type.tInt)) { - if (opType.baseType == Type.tInt) { - setE(expr, LLVMBuildSRemInt32(g_builder, types, left, right, "")) - } else { - setE(expr, LLVMBuildSRem(g_builder, left, right, "")) - } + setE(expr, LLVMBuildSRem(g_builder, left, right, "")) } elif (opType.isUnsignedInteger || (opType.isVectorType && opType.vectorBaseType == Type.tUInt)) { - if (opType.baseType == Type.tUInt) { - setE(expr, LLVMBuildURemUInt32(g_builder, types, left, right, "")) - } else { - setE(expr, LLVMBuildURem(g_builder, left, right, "")) - } + setE(expr, LLVMBuildURem(g_builder, left, right, "")) } elif (opType.isFloatOrDouble || (opType.isVectorType && opType.vectorBaseType == Type.tFloat)) { setE(expr, LLVMBuildFRem(g_builder, left, right, "")) } else { @@ -3274,17 +3266,9 @@ class public LlvmJitVisitor : AstVisitor { } } elif (expr.op == "%=") { if (opType.isSignedInteger || (opType.isVectorType && opType.vectorBaseType == Type.tInt)) { - if (opType.baseType == Type.tInt) { - setE(expr, LLVMBuildStoreAligned(g_builder, LLVMBuildSRemInt32(g_builder, types, r2v_left, right, ""), left, uint(expr.left._type.alignOf))) - } else { - setE(expr, LLVMBuildStoreAligned(g_builder, LLVMBuildSRem(g_builder, r2v_left, right, ""), left, uint(expr.left._type.alignOf))) - } + setE(expr, LLVMBuildStoreAligned(g_builder, LLVMBuildSRem(g_builder, r2v_left, right, ""), left, uint(expr.left._type.alignOf))) } elif (opType.isUnsignedInteger || (opType.isVectorType && opType.vectorBaseType == Type.tUInt)) { - if (opType.baseType == Type.tUInt) { - setE(expr, LLVMBuildStoreAligned(g_builder, LLVMBuildURemUInt32(g_builder, types, r2v_left, right, ""), left, uint(expr.left._type.alignOf))) - } else { - setE(expr, LLVMBuildStoreAligned(g_builder, LLVMBuildURem(g_builder, r2v_left, right, ""), left, uint(expr.left._type.alignOf))) - } + setE(expr, LLVMBuildStoreAligned(g_builder, LLVMBuildURem(g_builder, r2v_left, right, ""), left, uint(expr.left._type.alignOf))) } elif (opType.isFloatOrDouble || (opType.isVectorType && opType.vectorBaseType == Type.tFloat)) { setE(expr, LLVMBuildStoreAligned(g_builder, LLVMBuildFRem(g_builder, r2v_left, right, ""), left, uint(expr.left._type.alignOf))) } else { diff --git a/modules/dasLLVM/daslib/llvm_jit_run.das b/modules/dasLLVM/daslib/llvm_jit_run.das index efdbcf9153..a4671d6d67 100644 --- a/modules/dasLLVM/daslib/llvm_jit_run.das +++ b/modules/dasLLVM/daslib/llvm_jit_run.das @@ -36,7 +36,7 @@ var LINK_WHOLE_LIB = false // when true, standalone exe links against the whole // invalidates cached DLLs (e.g. edits to llvm_jit.das, llvm_macro.das, llvm_jit_common.das, // runtime helper ABI, default target triple). Cache filenames fold this in, so a bump // makes every previously written DLL miss the cache on the next run and get GC'd. -let LLVM_JIT_CODEGEN_VERSION : uint64 = 0x69ul // every string argument of an extern is substituted, not just the ones which asked (0x68: the grid formats' gemv applies signs as one masked negate per weight vector) +let LLVM_JIT_CODEGEN_VERSION : uint64 = 0x6aul // srem/urem for 32-bit % (0x69: every string argument of an extern is substituted, not just the ones which asked) // Read by tests-cpp/small/test_jit_emitter_pin.cpp: FNV-1a64 of the emitter sources // (normalized to LF; file list in the test) @@ -187,6 +187,8 @@ def jit_env_salt(opt_level : int; size_level : int; emit_prologue : bool; debug_ } h = (h ^ uint64(CONTEXT_OFFSET_OF_GLOBALS)) * JIT_FNV_PRIME h = (h ^ uint64(CONTEXT_OFFSET_OF_SHARED)) * JIT_FNV_PRIME + h = (h ^ uint64(CONTEXT_OFFSET_OF_STOP_FLAGS)) * JIT_FNV_PRIME + h = (h ^ uint64(CONTEXT_OFFSET_OF_EVAL_TOP)) * JIT_FNV_PRIME // full host-CPU identity when the TargetMachine bakes it - the cpuid bits below cover only the matrix-tier features; a wider box's object is an illegal instruction on a narrower one if (use_host_cpu) { let host_cpu = LLVMGetHostCPUName() From b12e888c0cc667678df492088a4aed7d11c7bb84 Mon Sep 17 00:00:00 2001 From: Churkin Aleksey Date: Tue, 1 Sep 2026 10:18:04 +0300 Subject: [PATCH 4/4] jit: make [jit] work in a host that ships C++ AOT run_jit returned early for any AOT program, and AOT emitted a direct C++ call that could never reach a jitted body. The early return now respects jit_enabled. Selection narrows to what linkCppAot left unbound - it runs before the simulate macros, so SimFunction.aot marks the covered set - plus anything marked [jit]; jitting the covered set would displace a native body at full codegen cost per load. A [jit] function takes the hybrid call form, which dispatches aot, then jit, then interpreted, and is what makes a re-jit visible to callers. The backend's own daslib modules opt out of the host's script policies: a game sets no_global_variables, which they legitimately trip. llvm_tune goes the other way on aliasing. read_manifest returned the table it built from json values, and a returned table of strings type-matches the string inside json's null sentinel, so the pass read it as the result aliasing that global. It fills an out-param and returns found instead, which removes the aliasing rather than the diagnostic, and the module now compiles with no_aliasing ON. Verified in a game host built with C++ AOT plus the JIT runtime. Co-Authored-By: Claude Opus 5 --- daslib/aot_cpp.das | 2 +- doc/reflections/das2rst.das | 2 +- include/daScript/simulate/aot_builtin_jit.h | 1 + modules/dasLLVM/daslib/llvm_jit_code.das | 1 + modules/dasLLVM/daslib/llvm_jit_intrin.das | 1 + modules/dasLLVM/daslib/llvm_jit_run.das | 21 +++++++++++--------- modules/dasLLVM/daslib/llvm_user_modules.das | 1 + src/builtin/module_builtin_runtime.cpp | 8 ++++++++ 8 files changed, 26 insertions(+), 11 deletions(-) diff --git a/daslib/aot_cpp.das b/daslib/aot_cpp.das index 4ec1984e3d..419a4a8c71 100644 --- a/daslib/aot_cpp.das +++ b/daslib/aot_cpp.das @@ -3798,7 +3798,7 @@ class public CppAot : AstVisitor { } return empty(bif.cppName); } - if (func.flags.noAot || func.flags.aotHybrid) return true; + if (func.flags.noAot || func.flags.aotHybrid || func.moreFlags.requestJit) return true; return func._module != program.getThisModule; } def needsArgPassType(argType : TypeDeclPtr) { diff --git a/doc/reflections/das2rst.das b/doc/reflections/das2rst.das index a44c97aa96..17aa62477d 100644 --- a/doc/reflections/das2rst.das +++ b/doc/reflections/das2rst.das @@ -207,7 +207,7 @@ get_value|insert_clone|emplace_new|insert_default|emplace_default|get_with_defau group_by_regex("Bit operations", mod, %regex~(popcnt|clz|ctz|mul128|__bit_set)$%%), group_by_regex("Intervals", mod, %regex~(interval)$%%), group_by_regex("RTTI", mod, %regex~(class_rtti_size)$%%), - hide_group(group_by_regex("Jit", mod,%regex~(invoke_code|.*jit.*)$%%)), + hide_group(group_by_regex("Jit", mod,%regex~(invoke_code|is_aot_function|.*jit.*)$%%)), group_by_regex("Initialization and finalization", mod, %regex~(using|clone|finalize)$%%), group_by_regex("Algorithms", mod, %regex~(swap|iter_range|count|ucount|long_iter_range)$%%), group_by_regex("Memset", mod, %regex~(memset.*)$%%), diff --git a/include/daScript/simulate/aot_builtin_jit.h b/include/daScript/simulate/aot_builtin_jit.h index c689cfe6ea..699c649bde 100644 --- a/include/daScript/simulate/aot_builtin_jit.h +++ b/include/daScript/simulate/aot_builtin_jit.h @@ -18,6 +18,7 @@ namespace das { float4 das_invoke_code ( void * pfun, vec4f anything, void * cmres, Context * context ); bool das_is_jit_function ( const Func func ); + bool das_is_aot_function ( const Func func ); bool das_has_jit_fastpath ( const Func func ); bool das_remove_jit ( const Func func ); bool das_instrument_jit ( void * pfun, const Func func, const LineInfo & info, Context & context ); diff --git a/modules/dasLLVM/daslib/llvm_jit_code.das b/modules/dasLLVM/daslib/llvm_jit_code.das index 2cc9f5b840..4cf992501d 100644 --- a/modules/dasLLVM/daslib/llvm_jit_code.das +++ b/modules/dasLLVM/daslib/llvm_jit_code.das @@ -1,6 +1,7 @@ options gen2 options indenting = 4 options strict_smart_pointers = false +options no_global_variables = false module llvm_jit_code shared private diff --git a/modules/dasLLVM/daslib/llvm_jit_intrin.das b/modules/dasLLVM/daslib/llvm_jit_intrin.das index 63a36cb51b..b40a72438d 100644 --- a/modules/dasLLVM/daslib/llvm_jit_intrin.das +++ b/modules/dasLLVM/daslib/llvm_jit_intrin.das @@ -7,6 +7,7 @@ options unsafe_table_lookup = false // intrinsic emitters carry per-function lowering contracts (gates, exactness preconditions, // saturation bounds) that don't compress to the 3-line cap — keep them at the emitters options _comment_hygiene = false +options no_global_variables = false module llvm_jit_intrin shared private diff --git a/modules/dasLLVM/daslib/llvm_jit_run.das b/modules/dasLLVM/daslib/llvm_jit_run.das index a4671d6d67..ad834cac89 100644 --- a/modules/dasLLVM/daslib/llvm_jit_run.das +++ b/modules/dasLLVM/daslib/llvm_jit_run.das @@ -2,6 +2,7 @@ options gen2 options indenting = 4 options strict_smart_pointers = false options stack = 4_194_304 +options no_global_variables = false require llvm/daslib/llvm_boost require llvm/daslib/llvm_dll_utils @@ -40,7 +41,7 @@ let LLVM_JIT_CODEGEN_VERSION : uint64 = 0x6aul // srem/urem for 32-bit % (0x69 // Read by tests-cpp/small/test_jit_emitter_pin.cpp: FNV-1a64 of the emitter sources // (normalized to LF; file list in the test) -let LLVM_JIT_EMITTER_HASH : uint64 = 0x66bdab0410220016ul +let LLVM_JIT_EMITTER_HASH : uint64 = 0x636870d5283981a3ul let JIT_FNV_PRIME : uint64 = 1099511628211ul @@ -695,17 +696,21 @@ def private run_split_codegen(prog : Program?; ctx : Context?; funcs : array var disabled : array var visitorDisabled = 0 @@ -829,7 +834,7 @@ def public run_jit(prog : Program?; var ctx : Context?) : bool { // nolint:STYL // In object mode emit the whole used, non-noAot set (all modules + // per-program generic instantiations), mirroring C++ AOT whole-program // coverage; noAot functions interpret as Program::linkCppAot skips them. - if (fun.flags.used && (jit_all_functions || rqj) && (!emit_aot_object || !fun.flags.noAot)) { + if (fun.flags.used && (!emit_aot_object || !fun.flags.noAot) && jit_selects(fun, rqj, aot_host, jit_all_functions, ctx)) { if (!fun.moreFlags.requestNoJit) { disableJitVisitor.disable = false // if DisableJitVisitor ever grows a preVisitFunction check again, it must @@ -1067,9 +1072,7 @@ def public run_jit(prog : Program?; var ctx : Context?) : bool { // nolint:STYL jit_log_report(funcs, recompile_prog, opt_level, tier_known, jit_split, split_parts, log_jit_time, totalTime, t_hash, t_init, t_declare, t_probe, t_irgen, t_opt, t_emit, t_install, t_finalize) } else { - // a -jit run that jitted NOTHING is indistinguishable from jit working (just slower) - - // the whole-program interpret must be loud, not inferred from timings - to_log(LOG_ERROR, "LLVM JIT: 0 functions to jit ({length(disabled)} marked no_jit, {visitorDisabled} disabled by content) - the WHOLE program runs interpreted\n") + to_log(LOG_INFO, "LLVM JIT: 0 functions to jit ({length(disabled)} marked no_jit, {visitorDisabled} disabled by content) - the WHOLE program runs interpreted\n") } return true } diff --git a/modules/dasLLVM/daslib/llvm_user_modules.das b/modules/dasLLVM/daslib/llvm_user_modules.das index 593bd4307f..5a6ff6ec41 100644 --- a/modules/dasLLVM/daslib/llvm_user_modules.das +++ b/modules/dasLLVM/daslib/llvm_user_modules.das @@ -1,6 +1,7 @@ options gen2 options indenting = 4 options _dasllama_internal = true +options no_global_variables = false module llvm_user_modules shared private diff --git a/src/builtin/module_builtin_runtime.cpp b/src/builtin/module_builtin_runtime.cpp index c7a5f83cb4..6ccc46d03d 100644 --- a/src/builtin/module_builtin_runtime.cpp +++ b/src/builtin/module_builtin_runtime.cpp @@ -1981,6 +1981,11 @@ namespace das return simfn->code && simfn->code->rtti_node_isJit(); } + bool das_is_aot_function ( const Func func ) { + auto simfn = func.PTR; + return simfn && simfn->aot; + } + bool das_jit_enabled ( Context * context, LineInfoArg * at ) { if ( !context->thisProgram ) context->throw_error_at(at, "can only query for jit during compilation"); return context->thisProgram->policies.jit_enabled; @@ -2848,6 +2853,9 @@ namespace das addExternInline(*this, lib, "is_jit_function", SideEffects::worstDefault, "das_is_jit_function") ->args({"function"}); + addExternInline(*this, lib, "is_aot_function", + SideEffects::worstDefault, "das_is_aot_function") + ->args({"function"}); addExternInline(*this, lib, "jit_enabled", SideEffects::none, "das_jit_enabled") ->args({"context","at"});