From ffb9f8ab934a55019009ba7943c032e08acca39a Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Sat, 3 Oct 2026 22:36:06 +0100 Subject: [PATCH] A global initialised from another global runs after it (#446) Every global whose initializer runs code got a constructor at the same priority, 1000 (LAST_GLOBAL_CONSTRUCTOR_PRIORITY). LLVM leaves the order of equal priorities undefined, and GlobalOpt uses that: optimised, `b`'s constructor in `let a = [1, 2]; let b = a;` was evaluated before `a`'s and read zeroes. The JIT's __mlir_gctors put each constructor at the start of the function, so it ran them in reverse. Each constructor now gets a priority one above every one before it, in MLIRGen and in GlobalOpLowering, and the JIT inserts each call in priority order (after the calls it already holds with a lower or equal priority). Closes #446 Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/LowerToLLVM.cpp | 83 ++++++++++++------- tslang/lib/TypeScript/MLIRGenModule.cpp | 11 ++- tslang/test/tester/CMakeLists.txt | 3 + .../test/tester/tests/00global_from_global.ts | 33 ++++++++ 4 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 tslang/test/tester/tests/00global_from_global.ts diff --git a/tslang/lib/TypeScript/LowerToLLVM.cpp b/tslang/lib/TypeScript/LowerToLLVM.cpp index 454eb7531..ee821d6e3 100644 --- a/tslang/lib/TypeScript/LowerToLLVM.cpp +++ b/tslang/lib/TypeScript/LowerToLLVM.cpp @@ -61,6 +61,13 @@ struct TsLlvmContext TsLlvmContext(CompileOptions& compileOptions) : compileOptions(compileOptions) {}; CompileOptions& compileOptions; + + // the priority of the next global's own constructor, above every one MLIRGen made: LLVM leaves + // the order of equal priorities undefined, and a global may read one made before it (#446) + int64_t nextGlobalConstructorPriority = LAST_GLOBAL_CONSTRUCTOR_PRIORITY; + + // the calls put into __mlir_gctors so far, with their priorities, to keep them in that order + std::vector> globalConstructorCalls; }; template class TsLlvmPattern : public OpConversionPattern @@ -4511,7 +4518,8 @@ struct GlobalOpLowering : public TsLlvmPattern lch.seekLastOp(parentModule.getBody()); rewriter.create(loc, - FlatSymbolRefAttr::get(rewriter.getContext(), StringRef(name)), rewriter.getIndexAttr(1000)); + FlatSymbolRefAttr::get(rewriter.getContext(), StringRef(name)), + rewriter.getIndexAttr(tsLlvmContext->nextGlobalConstructorPriority++)); } } else @@ -6725,6 +6733,27 @@ class SwitchStateInternalOpLowering : public TsLlvmPattern(op)) + { + return &llvmFunc.getBody(); + } + + if (auto tsFunc = dyn_cast_or_null(op)) + { + return &tsFunc.getBody(); + } + + if (auto func = dyn_cast_or_null(op)) + { + return &func.getBody(); + } + + return nullptr; +} + struct GlobalConstructorOpLowering : public TsLlvmPattern { using TsLlvmPattern::TsLlvmPattern; @@ -6762,51 +6791,37 @@ struct GlobalConstructorOpLowering : public TsLlvmPattern(loc, TypeRange{}, globalConstructorOp.getGlobalNameAttr(), ValueRange{}); + auto callOp = rewriter.create(loc, TypeRange{}, globalConstructorOp.getGlobalNameAttr(), ValueRange{}); + tsLlvmContext->globalConstructorCalls.push_back({(int64_t)priority, callOp}); rewriter.create(loc, ValueRange{}); } - else if (auto llvmGCtors = dyn_cast_or_null(mlirGCtors)) + else if (auto *gctorsBody = gctorsBodyOf(mlirGCtors)) { + // in priority order, each after the ones before it (#446): before the first call + // already here with a higher priority, else after the last one here OpBuilder::InsertionGuard insertGuard(rewriter); - if (priority <= 1000) + auto &calls = tsLlvmContext->globalConstructorCalls; + auto later = llvm::find_if(calls, [&](auto &entry) { return entry.first > (int64_t)priority; }); + if (later != calls.end()) { - rewriter.setInsertionPointToStart(&llvmGCtors.getBody().front()); + rewriter.setInsertionPoint(later->second); } - else + else if (!calls.empty()) { - rewriter.setInsertionPoint(llvmGCtors.getBody().back().getTerminator()); + rewriter.setInsertionPointAfter(calls.back().second); } - - rewriter.create(loc, TypeRange{}, globalConstructorOp.getGlobalNameAttr(), ValueRange{}); - } - else if (auto tsFuncGCtors = dyn_cast_or_null(mlirGCtors)) - { - OpBuilder::InsertionGuard insertGuard(rewriter); - if (priority <= 1000) + else if (priority <= 1000) { - rewriter.setInsertionPointToStart(&tsFuncGCtors.getBody().front()); + rewriter.setInsertionPointToStart(&gctorsBody->front()); } else { - rewriter.setInsertionPoint(tsFuncGCtors.getBody().back().getTerminator()); + rewriter.setInsertionPoint(gctorsBody->back().getTerminator()); } - rewriter.create(loc, TypeRange{}, globalConstructorOp.getGlobalNameAttr(), ValueRange{}); + auto callOp = rewriter.create(loc, TypeRange{}, globalConstructorOp.getGlobalNameAttr(), ValueRange{}); + calls.insert(later, {(int64_t)priority, callOp}); } - else if (auto funcGCtors = dyn_cast_or_null(mlirGCtors)) - { - OpBuilder::InsertionGuard insertGuard(rewriter); - if (priority <= 1000) - { - rewriter.setInsertionPointToStart(&funcGCtors.getBody().front()); - } - else - { - rewriter.setInsertionPoint(funcGCtors.getBody().back().getTerminator()); - } - - rewriter.create(loc, TypeRange{}, globalConstructorOp.getGlobalNameAttr(), ValueRange{}); - } else { assert(false); @@ -8127,6 +8142,12 @@ void TypeScriptToLLVMLoweringPass::runOnOperation() // The only remaining operation to lower from the `typescript` dialect, is the PrintOp. TsLlvmContext tsLlvmContext{tsContext.compileOptions}; + for (auto existing : m.getBody()->getOps()) + { + tsLlvmContext.nextGlobalConstructorPriority = + std::max(tsLlvmContext.nextGlobalConstructorPriority, existing.getPriority().getSExtValue() + 1); + } + patterns.insert< AddressOfOpLowering, ArithmeticUnaryOpLowering, ArithmeticBinaryOpLowering, AssertOpLowering, CastOpLowering, ConstantOpLowering, DefaultOpLowering, diff --git a/tslang/lib/TypeScript/MLIRGenModule.cpp b/tslang/lib/TypeScript/MLIRGenModule.cpp index 2701423d3..01721c051 100644 --- a/tslang/lib/TypeScript/MLIRGenModule.cpp +++ b/tslang/lib/TypeScript/MLIRGenModule.cpp @@ -728,9 +728,16 @@ namespace mlirgen builder.setInsertionPointToStart(theModule.getBody()); mclh.seekLastOp(theModule.getBody()); + // each one after every one before it: LLVM leaves the order of equal priorities undefined, + // and a global's initializer may read one made before it (#446) + int64_t priority = LAST_GLOBAL_CONSTRUCTOR_PRIORITY; + for (auto existing : theModule.getBody()->getOps()) + { + priority = std::max(priority, existing.getPriority().getSExtValue() + 1); + } + builder.create( - location, mlir::FlatSymbolRefAttr::get(builder.getContext(), funcName), - builder.getIndexAttr(LAST_GLOBAL_CONSTRUCTOR_PRIORITY)); + location, mlir::FlatSymbolRefAttr::get(builder.getContext(), funcName), builder.getIndexAttr(priority)); } mlir::LogicalResult MLIRGenImpl::generateGlobalEntryCode(mlir::Location location, NodeArray statements, diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 02252157e..b8e6c5312 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -478,6 +478,7 @@ tslang_add_test(NAME test-compile-00-nullable-to-any COMMAND test-runner "${PROJ tslang_add_test(NAME test-compile-00-ternary-new-or-null COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00ternary_new_or_null.ts") tslang_add_test(NAME test-compile-00-class COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class.ts") tslang_add_test(NAME test-compile-00-class-new COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_new.ts") +tslang_add_test(NAME test-compile-00-global-from-global COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_from_global.ts") tslang_add_test(NAME test-compile-00-try-cleanup-then-catch COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_cleanup_then_catch.ts") tslang_add_test(NAME test-compile-00-global-union-from-call COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_union_from_call.ts") tslang_add_test(NAME test-compile-00-capture-in-new-arguments COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00capture_in_new_arguments.ts") @@ -970,6 +971,7 @@ tslang_add_test(NAME test-jit-00-nullable-to-any COMMAND test-runner -jit "${PRO tslang_add_test(NAME test-jit-00-ternary-new-or-null COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00ternary_new_or_null.ts") tslang_add_test(NAME test-jit-00-class COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class.ts") tslang_add_test(NAME test-jit-00-class-new COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_new.ts") +tslang_add_test(NAME test-jit-00-global-from-global COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_from_global.ts") tslang_add_test(NAME test-jit-00-try-cleanup-then-catch COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_cleanup_then_catch.ts") tslang_add_test(NAME test-jit-00-global-union-from-call COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_union_from_call.ts") tslang_add_test(NAME test-jit-00-capture-in-new-arguments COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00capture_in_new_arguments.ts") @@ -1781,6 +1783,7 @@ set(TSLANG_CORPUS 00if_return.ts 00in_method_names.ts 00in.ts + 00global_from_global.ts 00try_cleanup_then_catch.ts 00global_object_literal.ts 00infer.ts diff --git a/tslang/test/tester/tests/00global_from_global.ts b/tslang/test/tester/tests/00global_from_global.ts new file mode 100644 index 000000000..661ba2378 --- /dev/null +++ b/tslang/test/tester/tests/00global_from_global.ts @@ -0,0 +1,33 @@ +// A module-level variable initialised from another one reads that one's value (#446). Each global +// that runs code gets a constructor, all at the same priority, and LLVM leaves the order of equal +// priorities undefined: optimised, `b`'s constructor was folded before `a`'s and read garbage; the +// JIT ran them in reverse. +class C { + v = 5; +} + +function mk(v: number) { + const c = new C(); + c.v = v; + return c; +} + +let a: number[] = [1, 2]; +let b = a; +const c = new C(); +const d = c; +const r = mk(7); +let e: C | null = r; +let s = "text" + 1; +let t = s; +let n = a.length + 1; + +function main() { + assert(b.length == 2 && b[1] == 2, "an array global from another"); + assert(d.v == 5, "a class global from another"); + assert(e !== null && e.v == 7, "a union global from another"); + assert(t == "text1", "a string global from another"); + assert(n == 3, "a number computed from another global"); + + print("done."); +}