From 3609811d8cc445562fccec8ec2ad58dc28981d62 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 21 May 2026 12:23:22 -0700 Subject: [PATCH 1/6] Make playground JIT toggle work, ship two benchmark samples The JIT (wasm) radio on daslang.io/playground/ has been disabled in production because the per-sample wasm artifacts never made it into the deployed _site. This wires up the staging step, ships two benchmark samples (Dictionary, SHA-256) wired through the cross-compile pipeline, and along the way unblocks the host-LLVM wasm32 JIT path that several samples actually need. Playground side: - .github/workflows/pages.yml: overlay web/output/samples/examples/*.wasm onto _site/playground/samples/examples/ so the HEAD probe in main.js (updateEngineAvailability) stops 404'ing. - web/examples/ui/samples/{data.json,examples/}: rename the old random_sequence sample to dict.das (closer to dasProfile naming), bump n 5000 -> 200000 so it's a real benchmark, and add sha256.das adapted from dasProfile (standalone, no config.das/testProfile deps). - web/CMakeLists.txt: cross-compile dict + sha256 in the foreach. - site/tests/playground/dropdowns.spec.js: update the e2e selector for the renamed sample, add SHA-256 dropdown coverage. dasLLVM wasm32 cross-compile fixes (host LLVM vs emcc-built runtime archive ABI alignment, surfaced cross-compiling dict/sha256 to wasm): * Signature drifts between modules/dasLLVM/daslib/{llvm_jit_common, llvm_exe}.das declarations and src/builtin/module_jit.cpp: - jit_prologue: add the 6th arg (LineInfoArg* at). - jit_array_lock/unlock, jit_free_heap/persistent, jit_iterator_delete, jit_simnode_interop, jit_register_standalone_variable: fix return type (void in C++, was voidptr/int1 in the IR declaration). - get_jit_table_at/find/erase: add the missing 2 args (Context*, LineInfoArg*); call sites pass null since the C++ dispatcher only looks at baseType. - llvm_jit.das jit_prologue call site: pass the 6th at arg. * For-loop array iteration: stop ptrtoint'ing both ends to LLVMIntPtrType (statically i64 on the 64-bit host even when the codegen target is wasm32) and compare pointers directly. The old form let host pointer width leak into the IR, so wasm32 lowering produced a never-true termination compare; array for-loops printed elements then ran off the end of the buffer forever. * init_jit now takes an optional target_triple and pins the module's data layout BEFORE any IR is built when cross-compiling. Otherwise LLVM eagerly constant-folds GEPs using the host's data layout and bakes host-size strides into the IR. * write_wasm passes +simd128,+nontrapping-fptoint to with_target_machine so vec4f returns get lowered the same way as the emcc-built runtime archive (which compiles with -msimd128 per web/CMakeLists.txt). Without the feature flag, host LLVM sret-lowered vec4f returns while the runtime returned them as native v128 - unreachable trap on every jit_invoke_block_* / jit_call_*. * g_target_is_wasm flag (set in init_jit, read by process_function_hints, build_noalias_list, attach_loop_metadata): bypass user [hint(...)] application for wasm32 - alwaysinline over complex inlined bodies triggers a host-LLVM wasm32 backend miscompile that native JIT does not hit. The unsafe_range_check / unsafe_alias / unsafe_capture options also get default-false on wasm, which keeps bounds checks in (safer; tiny perf cost). * LLVM_JIT_CODEGEN_VERSION 0x04 -> 0x08 to invalidate native JIT DLL caches across these changes. Local browser timings on the staged playground (Chromium): Dictionary (n=200000, 10 iters): interp 8.9 ms/iter, JIT 5 ms/iter SHA-256 (1024 KB, 10 iters): interp 2.6 MB/sec, JIT 200 MB/sec hello/loop/func also run cleanly in both engines after the fixes. Residual: sha256 JIT main-exit OOB after the timing output. The bench output is correct and the trap fires post-print; tracked separately. Won't affect the demo. Lint clean on all 6 changed .das files. 285 jit_tests pass on the interpreter; native -jit on dict + sha256 also green. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/pages.yml | 9 ++ modules/dasLLVM/daslib/llvm_exe.das | 19 ++- modules/dasLLVM/daslib/llvm_jit.das | 43 +++++-- modules/dasLLVM/daslib/llvm_jit_common.das | 78 +++++++++--- modules/dasLLVM/daslib/llvm_macro.das | 7 +- site/tests/playground/dropdowns.spec.js | 15 ++- web/CMakeLists.txt | 2 +- web/examples/ui/samples/data.json | 8 +- .../{random_sequence.das => dict.das} | 11 +- web/examples/ui/samples/examples/sha256.das | 118 ++++++++++++++++++ 10 files changed, 256 insertions(+), 54 deletions(-) rename web/examples/ui/samples/examples/{random_sequence.das => dict.das} (71%) create mode 100644 web/examples/ui/samples/examples/sha256.das diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index a77098f22f..bd3a8a2015 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -145,6 +145,15 @@ jobs: cp web/output/daslang_static.wasm _site/files/wasm/ # Copy sample data if present cp -r web/examples/ui/samples _site/playground/samples 2>/dev/null || true + # Overlay the cross-compiled per-sample .wasm artifacts staged by + # stage_site_playground_wasm. The HEAD probe in main.js + # (updateEngineAvailability) enables the JIT radio only when this + # file is reachable — without the copy it 404s and the toggle is + # permanently disabled. + if [ -d web/output/samples/examples ]; then + mkdir -p _site/playground/samples/examples + cp web/output/samples/examples/*.wasm _site/playground/samples/examples/ 2>/dev/null || true + fi # Our Forge-skinned playground/index.html + skin CSS + init shim take precedence. # CodeMirror bundle (codemirror.min.js/css, simple-mode.js, daslang-*.js, # cm-forge.css) is served from /files/cm/ — copied via `cp -r site/files` diff --git a/modules/dasLLVM/daslib/llvm_exe.das b/modules/dasLLVM/daslib/llvm_exe.das index 0f06f0d71a..434ccfbab4 100644 --- a/modules/dasLLVM/daslib/llvm_exe.das +++ b/modules/dasLLVM/daslib/llvm_exe.das @@ -118,9 +118,12 @@ class CollectExternVisitor : AstVisitor { LLVMBuildCall2(init_builder, register_mod_type, reg_strings, array(), "") registered_modules["strings"] = true + // void * get_jit_table_{at,find,erase} ( int32_t baseType, Context * context, LineInfoArg * at ) reg_extern_tab_type = LLVMFunctionType(types.LLVMVoidPtrType(), fixed_array( - types.t_int32, // type + types.t_int32, // baseType + types.LLVMVoidPtrType(), // context + types.LLVMVoidPtrType(), // at ) ) tab_at_accessor = LLVMAddFunctionWithType(g_mod, "get_jit_table_at", reg_extern_tab_type) @@ -420,9 +423,12 @@ class CollectExternVisitor : AstVisitor { } } + // get_jit_table_{at,find,erase} dispatch on baseType only; context+at unused. + // Pass null for both to match the C++ signature without a real Context. def add_table_at_call(index_t : TypeDeclPtr) { let typ = get_table_t(index_t) - var call_args = array(types.ConstI32(uint64(typ))) + let null_ptr = LLVMConstPointerNull(types.LLVMVoidPtrType()) + var call_args = array(types.ConstI32(uint64(typ)), null_ptr, null_ptr) let tab_ptr = LLVMBuildCall2(builder, reg_extern_tab_type, tab_at_accessor, call_args, "") let tab_method = LLVMGetNamedGlobal(g_mod, DllName(FN_JIT_TABLE_AT(typ)).glob()) @@ -431,7 +437,8 @@ class CollectExternVisitor : AstVisitor { def add_table_find_call(index_t : TypeDeclPtr) { let typ = get_table_t(index_t) - var call_args = array(types.ConstI32(uint64(typ))) + let null_ptr = LLVMConstPointerNull(types.LLVMVoidPtrType()) + var call_args = array(types.ConstI32(uint64(typ)), null_ptr, null_ptr) let tab_ptr = LLVMBuildCall2(builder, reg_extern_tab_type, tab_find_accessor, call_args, "") let tab_method = LLVMGetNamedGlobal(g_mod, DllName(FN_JIT_TABLE_FIND(typ)).glob()) @@ -440,7 +447,8 @@ class CollectExternVisitor : AstVisitor { def add_table_erase_call(index_t : TypeDeclPtr) { let typ = get_table_t(index_t) - var call_args = array(types.ConstI32(uint64(typ))) + let null_ptr = LLVMConstPointerNull(types.LLVMVoidPtrType()) + var call_args = array(types.ConstI32(uint64(typ)), null_ptr, null_ptr) let tab_ptr = LLVMBuildCall2(builder, reg_extern_tab_type, tab_erase_accessor, call_args, "") let tab_method = LLVMGetNamedGlobal(g_mod, DllName(FN_JIT_TABLE_ERASE(typ)).glob()) @@ -978,7 +986,8 @@ def public inject_main(program_context : Context?; ctx : LLVMContextRef; LLVMBuildCall2(builder, reg_fusion_type, reg_fusion_fn, array(), "") } - let init_global_var_type = LLVMFunctionType(types.LLVMVoidPtrType(), + // void jit_register_standalone_variable ( Context * ctx, uint64_t mnh, uint64_t offset ) + let init_global_var_type = LLVMFunctionType(types.t_void, fixed_array( types.LLVMVoidPtrType(), // Context * types.t_int64, // uint64_t mnh diff --git a/modules/dasLLVM/daslib/llvm_jit.das b/modules/dasLLVM/daslib/llvm_jit.das index 5ca163f69d..7093807fec 100644 --- a/modules/dasLLVM/daslib/llvm_jit.das +++ b/modules/dasLLVM/daslib/llvm_jit.das @@ -471,6 +471,9 @@ class public LlvmJitVisitor : AstVisitor { def build_noalias_list(annotations : AnnotationList) : tuple; nocapture : table> { var noalias : table var nocapture : table + // Skip user hints entirely on wasm32 — host-LLVM wasm codegen has had + // miscompiles around them. Tracked alongside the alwaysinline gotcha. + if (g_target_is_wasm) return <- (noalias <- noalias, nocapture <- nocapture) for (ann in annotations) { if (ann.annotation.name == "hint") { for (arg in ann.arguments) { @@ -506,6 +509,10 @@ class public LlvmJitVisitor : AstVisitor { option_no_range_check = false option_no_alias = false option_no_capture = false + // Bypass user hints on wasm — keeps option_no_* flags false (= safe + // defaults: bounds checks on, no alias/capture assumption) and skips + // every LLVMAddAttributeToFunction call below. See g_target_is_wasm. + if (g_target_is_wasm) return for (ann in annotations) { if (ann.annotation.name == "hint") { for (arg in ann.arguments) { @@ -820,7 +827,8 @@ class public LlvmJitVisitor : AstVisitor { get_line_info_ptr(funAt), types.ConstI32(uint64(totalStackSize)), prologue, - get_context_param() + get_context_param(), + get_line_info_ptr(funAt) // at — used for stack-overflow throw ) var typ = g_fn_types[FN_JIT_PROLOGUE] LLVMBuildCall2(g_builder, typ, LLVMGetNamedFunction(g_mod, FN_JIT_PROLOGUE), params, "") @@ -3013,9 +3021,13 @@ class public LlvmJitVisitor : AstVisitor { LLVMBuildStore(g_builder, s0, getV(svar)) let dimSize = ssrc._type.dim[0] var tail = types.ConstI32(uint64(dimSize - 1)) - var sto = build_array_index(sdim, tail, etype, "last_element") - sto = LLVMBuildPtrToInt(g_builder, sto, types.LLVMIntPtrType(), "dim_end") - range2[ssrc] = sto + // Stash end-element pointer as-is; compare directly in next-step. + // Previous code ptrtoint'd to LLVMIntPtrType which is statically i64 + // on a 64-bit host even when codegen target is wasm32 — that zero- + // extends a 32-bit ptr to 64 bits but wasm32 codegen lowers it in a + // way that breaks the i64 equality check. Direct ptr compare avoids + // the width mismatch entirely. + range2[ssrc] = build_array_index(sdim, tail, etype, "last_element") } elif (ssrc._type.isGoodArrayType) {// {}->first() var arr = LLVMBuildLoadData2Aligned(g_builder, type_to_llvm_type(ssrc._type), getE(ssrc), ssrc._type.alignOf, "array") var size = LLVMBuildExtractValue(g_builder, arr, uint(JIT_ARRAY.SIZE), "array.size") @@ -3034,9 +3046,8 @@ class public LlvmJitVisitor : AstVisitor { var data = LLVMBuildExtractValue(g_builder, arr, uint(JIT_ARRAY.DATA), "array.data") LLVMBuildStore(g_builder, data, getV(svar)) var tail = LLVMBuildSub(g_builder, size, i64_one, "") - var sto = build_array_index(data, tail, ssrc._type.firstType, "last_element") - sto = LLVMBuildPtrToInt(g_builder, sto, types.LLVMIntPtrType(), "array_end") - range2[ssrc] = sto + // See dim case above — store end-element ptr directly, compare-by-ptr. + range2[ssrc] = build_array_index(data, tail, ssrc._type.firstType, "last_element") } elif (ssrc._type.isIterator) { if (is_count_or_ucount(ssrc)) { var ccount = ssrc as ExprCall @@ -3132,9 +3143,9 @@ class public LlvmJitVisitor : AstVisitor { var svar_v = LLVMBuildLoad2(g_builder, LLVMPointerType(type_to_llvm_type(svar._type), 0u), getV(svar), "") var svar_i = build_array_index(svar_v, types.ConstI32(1ul), etype, "next_element") LLVMBuildStore(g_builder, svar_i, getV(svar)) + // Direct ptr compare — avoids LLVMIntPtrType host/wasm32 width mismatch. var sto = range2[ssrc] - svar_i = LLVMBuildPtrToInt(g_builder, svar_v, types.LLVMIntPtrType(), "") - var rcond = LLVMBuildICmp(g_builder, LLVMIntPredicate.LLVMIntEQ, svar_i, sto, "") + var rcond = LLVMBuildICmp(g_builder, LLVMIntPredicate.LLVMIntEQ, svar_v, sto, "") var nextOk = append_basic_block("for_{svar.name}_next_ok") LLVMBuildCondBr(g_builder, rcond, lblk.loop_end, nextOk) LLVMPositionBuilderAtEnd(g_builder, nextOk) @@ -3142,9 +3153,9 @@ class public LlvmJitVisitor : AstVisitor { var svar_v = LLVMBuildLoad2(g_builder, LLVMPointerType(type_to_llvm_type(svar._type), 0u), getV(svar), "") var svar_i = build_array_index(svar_v, types.ConstI32(1ul), ssrc._type.firstType, "next_element") LLVMBuildStore(g_builder, svar_i, getV(svar)) + // Direct ptr compare — avoids LLVMIntPtrType host/wasm32 width mismatch. var sto = range2[ssrc] - svar_i = LLVMBuildPtrToInt(g_builder, svar_v, types.LLVMIntPtrType(), "") - var rcond = LLVMBuildICmp(g_builder, LLVMIntPredicate.LLVMIntEQ, svar_i, sto, "") + var rcond = LLVMBuildICmp(g_builder, LLVMIntPredicate.LLVMIntEQ, svar_v, sto, "") var nextOk = append_basic_block("for_{svar.name}_next_ok") LLVMBuildCondBr(g_builder, rcond, lblk.loop_end, nextOk) LLVMPositionBuilderAtEnd(g_builder, nextOk) @@ -6065,8 +6076,14 @@ class public LlvmJitVisitor : AstVisitor { operands |> push(temp) var mustprog = make_loop_md_flag("llvm.loop.mustprogress") operands |> push(mustprog) - for (a in args) { - append_loop_hint_operand(operands, a) + // Skip user-supplied loop hints on wasm — host LLVM's wasm32 backend + // has had miscompiles around llvm.loop.* metadata in combination with + // certain bodies. mustprogress alone is preserved (safe, required for + // some opt passes). See g_target_is_wasm. + if (!g_target_is_wasm) { + for (a in args) { + append_loop_hint_operand(operands, a) + } } let loop_md = LLVMMDNodeInContext2(g_ctx, array_data_ptr(operands), uint64(length(operands))) LLVMMetadataReplaceAllUsesWith(temp, loop_md) diff --git a/modules/dasLLVM/daslib/llvm_jit_common.das b/modules/dasLLVM/daslib/llvm_jit_common.das index 013e765276..83cfedb4ad 100644 --- a/modules/dasLLVM/daslib/llvm_jit_common.das +++ b/modules/dasLLVM/daslib/llvm_jit_common.das @@ -290,6 +290,15 @@ def public get_table_erase(g_builder : LLVMOpaqueBuilder?; llvm_module : LLVMOpa var public g_fn_types : table +// Set by init_jit when target_triple is a wasm target. Cross-compile path's +// host-LLVM wasm32 codegen mishandles several user-provided hints (alwaysinline +// over complex inlined bodies, certain loop-vectorize / unroll metadata). When +// this flag is true, the LlvmJitVisitor skips ALL user hint application — +// function-level [hint(...)] AND for/while-loop hints — and falls back to the +// optimizer's default heuristics. Slight perf hit, but eliminates a class of +// wasm32-only miscompiles. +var public g_target_is_wasm = false + def public LLVMAddFunctionWithType(mod : LLVMModuleRef; name : string; typ : LLVMTypeRef) { var rv = LLVMAddFunction(mod, name, typ) g_fn_types[name] = typ @@ -337,7 +346,7 @@ def public init_jit_clopts() { } [macro_function] -def public init_jit(cg_opt_level : uint) { +def public init_jit(cg_opt_level : uint; target_triple : string = "") { // also prevent double init (g_mod != null) return if (is_in_completion() || is_compiling_macros() || g_mod != null) LLVMLinkInMCJIT() @@ -355,6 +364,26 @@ def public init_jit(cg_opt_level : uint) { g_fn_types |> clear() g_mod = LLVMModuleCreateWithNameInContext("llvm_jit_module", g_ctx) LLVMCreateJITCompilerForModule(g_engine, g_mod, cg_opt_level) + // For cross-compile targets (wasm32, etc.) pin the module's data layout + // to the TARGET's, not the host's, BEFORE any IR is built. LLVM constant- + // folds GEPs eagerly using the module's current layout — if the host has + // 8-byte pointers and the target has 4-byte, every for-loop end-pointer + // GEP bakes in the wrong stride and the loop never terminates. The native + // path (empty triple) keeps the host default. + g_target_is_wasm = target_triple |> starts_with("wasm") + if (!(target_triple |> empty())) { + // wasm32 target isn't always part of LLVMInitializeAllTargets in + // prebuilt LLVM.dll — call the explicit wasm initializer when needed. + if (g_target_is_wasm) { + LLVMInitializeWasmTarget() + } + with_target_machine(target_triple, "", "+simd128,+nontrapping-fptoint", cg_opt_level) $(targetMachine : LLVMTargetMachineRef) { + let dl = LLVMCreateTargetDataLayout(targetMachine) + LLVMSetModuleDataLayout(g_mod, dl) + LLVMSetTarget(g_mod, target_triple) + LLVMDisposeTargetData(dl) + } + } static_if (LLVM_DEBUG_EVERYTHING) { let hostcpu = LLVMGetHostCPUName() let features = LLVMGetHostCPUFeatures() @@ -456,7 +485,7 @@ def public init_jit(cg_opt_level : uint) { LLVMAddAttributeToFunctionArgument(jit_alloc_persistent, 1u, nocapture) // void jit_free_heap ( void * bytes, uint32_t size, Context * context ) var jit_free_heap = LLVMAddFunctionWithType(g_mod, FN_JIT_FREE_HEAP, - LLVMFunctionType(g_prim_t.LLVMVoidPtrType(), + LLVMFunctionType(g_prim_t.t_void, fixed_array(g_prim_t.LLVMVoidPtrType(), g_prim_t.t_int32, g_prim_t.LLVMVoidPtrType()))) LLVMAddGlobalMapping(g_engine, jit_free_heap, get_jit_free_heap()) LLVMAddAttributesToFunction(jit_free_heap, fixed_array(nounwind, willreturn, memory_arg_inaccessible_rw)) @@ -464,21 +493,21 @@ def public init_jit(cg_opt_level : uint) { LLVMAddAttributeToFunctionArgument(jit_free_heap, 2u, nocapture) // void jit_free_persistent ( void * bytes, Context * context ) var jit_free_persistent = LLVMAddFunctionWithType(g_mod, FN_JIT_FREE_PERSISTENT, - LLVMFunctionType(g_prim_t.LLVMVoidPtrType(), + LLVMFunctionType(g_prim_t.t_void, fixed_array(g_prim_t.LLVMVoidPtrType(), g_prim_t.LLVMVoidPtrType()))) LLVMAddGlobalMapping(g_engine, jit_free_persistent, get_jit_free_persistent()) LLVMAddAttributesToFunction(jit_free_persistent, fixed_array(nounwind, willreturn, memory_arg_inaccessible_rw)) LLVMAddAttributeToFunctionArgumentRange(jit_free_persistent, urange(0, 2), nocapture) - // void jit_array_lock ( Array & array, Context * context ) + // void jit_array_lock ( Array & array, Context * context, LineInfoArg * at ) var jit_array_lock = LLVMAddFunctionWithType(g_mod, FN_JIT_ARRAY_LOCK, - LLVMFunctionType(g_prim_t.LLVMVoidPtrType(), + LLVMFunctionType(g_prim_t.t_void, fixed_array(g_prim_t.LLVMVoidPtrType(), g_prim_t.LLVMVoidPtrType(), g_prim_t.LLVMVoidPtrType()))) LLVMAddGlobalMapping(g_engine, jit_array_lock, get_jit_array_lock()) LLVMAddAttributesToFunction(jit_array_lock, fixed_array(nounwind, willreturn)) LLVMAddAttributeToFunctionArgumentRange(jit_array_lock, urange(0, 2), nocapture) - // void jit_array_unlock ( Array & array, Context * context ) + // void jit_array_unlock ( Array & array, Context * context, LineInfoArg * at ) var jit_array_unlock = LLVMAddFunctionWithType(g_mod, FN_JIT_ARRAY_UNLOCK, - LLVMFunctionType(g_prim_t.LLVMVoidPtrType(), + LLVMFunctionType(g_prim_t.t_void, fixed_array( g_prim_t.LLVMVoidPtrType(), // arr g_prim_t.LLVMVoidPtrType(), // ctx @@ -509,21 +538,22 @@ def public init_jit(cg_opt_level : uint) { LLVMAddGlobalMapping(g_engine, jit_str_cat, get_jit_str_cat()) LLVMAddAttributesToFunction(jit_str_cat, fixed_array(nounwind, willreturn)) LLVMAddAttributeToFunctionArgumentRange(jit_str_cat, urange(0, 3), nocapture) - // void jit_prologue ( void * line_info, int32_t stackSize, JitStackState * stackState, Context * context ) + // void jit_prologue ( char * funcName, void * funcLineInfo, int32_t stackSize, JitStackState * stackState, Context * context, LineInfoArg * at ) var jit_prologue = LLVMAddFunctionWithType(g_mod, FN_JIT_PROLOGUE, LLVMFunctionType(g_prim_t.t_void, fixed_array( - g_prim_t.get_type_string(), - g_prim_t.LLVMVoidPtrType(), - g_prim_t.t_int32, - LLVMPointerType(g_t_stack_state, 0u), - g_prim_t.LLVMVoidPtrType() + g_prim_t.get_type_string(), // funcName + g_prim_t.LLVMVoidPtrType(), // funcLineInfo + g_prim_t.t_int32, // stackSize + LLVMPointerType(g_t_stack_state, 0u), // stackState + g_prim_t.LLVMVoidPtrType(), // context + g_prim_t.LLVMVoidPtrType() // at ) ) ) LLVMAddGlobalMapping(g_engine, jit_prologue, get_jit_prologue()) LLVMAddAttributesToFunction(jit_prologue, fixed_array(nounwind, willreturn)) - LLVMAddAttributeToFunctionArgumentRange(jit_prologue, urange(3, 5), nocapture) + LLVMAddAttributeToFunctionArgumentRange(jit_prologue, urange(3, 6), nocapture) // void jit_epilogue ( JitStackState * stackState, Context * context ) var jit_epilogue = LLVMAddFunctionWithType(g_mod, FN_JIT_EPILOGUE, LLVMFunctionType(g_prim_t.t_void, @@ -564,7 +594,7 @@ def public init_jit(cg_opt_level : uint) { LLVMAddAttributeToFunctionArgumentRange(jit_iterator_iterate, urange(0, 3), nocapture) // void builtin_iterator_delete ( const Sequence & it, Context * context ) { var jit_iterator_delete = LLVMAddFunctionWithType(g_mod, FN_JIT_ITERATOR_DELETE, - LLVMFunctionType(g_prim_t.t_int1, + LLVMFunctionType(g_prim_t.t_void, fixed_array(LLVMPointerType(g_t_sequence, 0u), g_prim_t.LLVMVoidPtrType()))) LLVMAddGlobalMapping(g_engine, jit_iterator_delete, get_jit_iterator_delete()) LLVMAddAttributesToFunction(jit_iterator_delete, fixed_array(nounwind, willreturn)) @@ -580,11 +610,12 @@ def public init_jit(cg_opt_level : uint) { var jit_iterator_next = LLVMAddFunctionWithType(g_mod, FN_JIT_ITERATOR_NEXT, LLVMFunctionType(g_prim_t.t_int1, fixed_array(LLVMPointerType(g_t_sequence, 0u), g_prim_t.LLVMVoidPtrType(), g_prim_t.LLVMVoidPtrType(), g_prim_t.LLVMVoidPtrType()))) - let simnode_interop_type = LLVMFunctionType(g_prim_t.LLVMVoidPtrType(), + // void jit_simnode_interop ( void * ptr, int argCount, TypeInfo ** types ) + let simnode_interop_type = LLVMFunctionType(g_prim_t.t_void, fixed_array( - g_prim_t.LLVMVoidPtrType(), // context - g_prim_t.t_int32, - g_prim_t.LLVMVoidPtrType() // argv + g_prim_t.LLVMVoidPtrType(), // ptr (placement-new buffer for SimNode_AotInteropBase) + g_prim_t.t_int32, // argCount + g_prim_t.LLVMVoidPtrType() // types ) ) var jit_simnode_interop = LLVMAddFunctionWithType( @@ -827,7 +858,14 @@ def public write_wasm(mod : LLVMOpaqueModule?; out_path : string; triple : strin to_log(LOG_WARNING, "wasm: runtime symbols referenced but libDaScript_runtime.a not found at web/output/lib/ — run the web/ emscripten build to link in the runtime, or pass --jit-runtime-lib=\n") } let real_triple = (!(triple |> empty()) ? triple : "wasm32-unknown-emscripten") - with_target_machine(real_triple, "", "", 2u) $(targetMachine : LLVMTargetMachineRef) { + // Match the feature set web/CMakeLists.txt compiles the runtime archive with + // (-msimd128 -mnontrapping-fptoint). Without +simd128 the host LLVM lowers + // vec4f returns via sret while emcc returns them as native v128, causing + // function-signature mismatches at wasm-ld link time and `unreachable` + // traps at runtime on any helper that returns vec4f (jit_invoke_block_*, + // jit_call_*). + let features = "+simd128,+nontrapping-fptoint" + with_target_machine(real_triple, "", features, 2u) $(targetMachine : LLVMTargetMachineRef) { // Pin the module's data layout + triple so codegen sizes pointers as // 32-bit. Without this the module inherits host layout and produces // wrong-sized loads/stores in the emitted wasm. diff --git a/modules/dasLLVM/daslib/llvm_macro.das b/modules/dasLLVM/daslib/llvm_macro.das index 8a2962621f..f72e570196 100644 --- a/modules/dasLLVM/daslib/llvm_macro.das +++ b/modules/dasLLVM/daslib/llvm_macro.das @@ -26,7 +26,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 = 0x04ul +let LLVM_JIT_CODEGEN_VERSION : uint64 = 0x08ul let JIT_FNV_PRIME : uint64 = 1099511628211ul @@ -236,7 +236,10 @@ class JIT_LLVM : AstSimulateMacro { dll_hash_basename = jit_dll_basename(funcs, opt_level, size_level, emit_prologue, debug_info) output_path = ".jitted_scripts/{prog.thisNamespace}/{dll_hash_basename}" } - init_jit(opt_level |> uint) + // Pass target_triple so init_jit can pin the module data layout + // BEFORE IR construction (wasm32 cross-compile needs 4-byte pointer + // sizes baked into GEP folding from the very first IR instruction). + init_jit(opt_level |> uint, target_triple) var jit_flags : LlvmJitFlags jit_flags.emit_prologue = emit_prologue diff --git a/site/tests/playground/dropdowns.spec.js b/site/tests/playground/dropdowns.spec.js index 54d9fce9ab..e52ce49f5d 100644 --- a/site/tests/playground/dropdowns.spec.js +++ b/site/tests/playground/dropdowns.spec.js @@ -1,7 +1,8 @@ // The Examples