From ce7ea2248279c375a8eb053101e635d6561cc0e4 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Sat, 18 Jul 2026 15:00:38 +0100 Subject: [PATCH] Fix crash for global const object methods during JIT materialization and add regression test --- tslang/lib/TypeScript/LowerToLLVM.cpp | 28 +++++++++++++++++-- tslang/test/tester/CMakeLists.txt | 2 ++ .../tests/00global_const_object_method.ts | 22 +++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 tslang/test/tester/tests/00global_const_object_method.ts diff --git a/tslang/lib/TypeScript/LowerToLLVM.cpp b/tslang/lib/TypeScript/LowerToLLVM.cpp index 75459eaef..ea06be3dc 100644 --- a/tslang/lib/TypeScript/LowerToLLVM.cpp +++ b/tslang/lib/TypeScript/LowerToLLVM.cpp @@ -3681,6 +3681,23 @@ struct GlobalOpLowering : public TsLlvmPattern LLVMCodeHelper lch(globalOp, rewriter, getTypeConverter(), tsLlvmContext->compileOptions); + // A ConstantOp whose *TupleType/ConstTupleType result* has a bound-method + // field (e.g. a plain `const obj = { ..., method() {} }` at global scope) + // lowers its method field via LLVM::AddressOfOp + LLVM::InsertValueOp (see + // LLVMCodeHelper::getTupleFromArrayAttr) -- not valid inside a static LLVM + // global initializer region, only inside real code (a function), so such a + // global must go through the constructor path below. Deliberately narrower + // than "any ConstantOp whose attribute contains a FlatSymbolRefAttr": that + // broader check also caught MSVC RTTI/EH type-descriptor globals (e.g. + // `??_R0PEAD@8`, which reference the `type_info` vtable symbol) that are + // NOT mlir_ts tuple types and were already lowering correctly as static + // data -- forcing those through the constructor path broke JIT symbol + // materialization for the whole module. + MLIRTypeHelper mth(rewriter.getContext(), tsLlvmContext->compileOptions); + auto isBoundMethodTupleConstant = [&](mlir_ts::ConstantOp constantOp) { + return mth.hasBoundMethodField(constantOp.getType()); + }; + auto createAsGlobalConstructor = false; // TODO: we need to write correct attributes to Ops and detect which ops should be in GlobalConstructor auto visitorAllOps = [&](Operation *op) { @@ -3700,9 +3717,16 @@ struct GlobalOpLowering : public TsLlvmPattern else if (auto castOp = dyn_cast(op)) { auto castType = castOp.getRes().getType(); - if (isa(castType) || isa(castType)) + if (isa(castType) || isa(castType)) + { + createAsGlobalConstructor = true; + } + } + else if (auto constantOp = dyn_cast(op)) + { + if (isBoundMethodTupleConstant(constantOp)) { - createAsGlobalConstructor = true; + createAsGlobalConstructor = true; } } diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 9ccc5a119..3b1e07dcb 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -125,6 +125,7 @@ add_test(NAME test-compile-00-bool-arith-ops COMMAND test-runner "${PROJECT_SOUR add_test(NAME test-compile-00-mixed-type-ops COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00mixed_type_ops.ts") add_test(NAME test-compile-00-generator-manual-next COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_manual_next.ts") add_test(NAME test-compile-00-generator-manual-next-2 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_manual_next2.ts") +add_test(NAME test-compile-00-global-const-object-method COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_const_object_method.ts") add_test(NAME test-compile-00-funcs COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs.ts") add_test(NAME test-compile-00-funcs-capture COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_capture.ts") add_test(NAME test-compile-00-funcs-vararg COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_vararg.ts") @@ -472,6 +473,7 @@ add_test(NAME test-jit-00-bool-arith-ops COMMAND test-runner -jit "${PROJECT_SOU add_test(NAME test-jit-00-mixed-type-ops COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00mixed_type_ops.ts") add_test(NAME test-jit-00-generator-manual-next COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_manual_next.ts") add_test(NAME test-jit-00-generator-manual-next-2 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_manual_next2.ts") +add_test(NAME test-jit-00-global-const-object-method COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_const_object_method.ts") add_test(NAME test-jit-00-funcs COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs.ts") add_test(NAME test-jit-00-funcs-capture COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_capture.ts") add_test(NAME test-jit-00-funcs-vararg COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_vararg.ts") diff --git a/tslang/test/tester/tests/00global_const_object_method.ts b/tslang/test/tester/tests/00global_const_object_method.ts new file mode 100644 index 000000000..1ae0072dc --- /dev/null +++ b/tslang/test/tester/tests/00global_const_object_method.ts @@ -0,0 +1,22 @@ +// regression test: a top-level (global) `const` object literal with a method that +// mutates `this` used to crash the compiler (0xC0000005) at JIT materialization time. +// Root cause: GlobalOpLowering's visitorAllOps walk decided whether a global needs a +// runtime constructor function or can be a plain static LLVM `constant` initializer, +// but had no case for a ConstantOp whose TupleType/ConstTupleType result has a +// bound-method field -- such a field lowers via LLVM::AddressOfOp + InsertValueOp, +// which is not valid inside a static global initializer region, only inside real code. +// `let` at global scope, and `const` declared locally inside a function, were both +// unaffected (they already go through -- or don't need -- the constructor path). +const obj = { + count: 0, + inc() { this.count = this.count + 1; }, + greet() { print("hi"); } +}; + +function main() { + obj.greet(); + obj.inc(); + obj.inc(); + assert(obj.count == 2); + print("done."); +}