diff --git a/tslang/include/TypeScript/TypeScriptOps.td b/tslang/include/TypeScript/TypeScriptOps.td index 818fdf6d5..92cb2a11d 100644 --- a/tslang/include/TypeScript/TypeScriptOps.td +++ b/tslang/include/TypeScript/TypeScriptOps.td @@ -2215,8 +2215,11 @@ def TypeScript_AddressOfOp : TypeScript_Op<"AddressOf"> { }]; } +// The initializer is one block as MLIRGen builds it, ending in ts.GlobalResult; lowering a branch +// in it (a conditional, an optional widened into a union) adds blocks, and GlobalOpLowering then +// makes the global a constructor (#462). def TypeScript_GlobalOp : TypeScript_Op<"Global", - [IsolatedFromAbove, SingleBlockImplicitTerminator<"GlobalResultOp">, Symbol]> { + [IsolatedFromAbove, Symbol]> { let arguments = (ins TypeAttr:$type, UnitAttr:$constant, diff --git a/tslang/lib/TypeScript/LowerToLLVM.cpp b/tslang/lib/TypeScript/LowerToLLVM.cpp index a01c87f55..7b3d5c128 100644 --- a/tslang/lib/TypeScript/LowerToLLVM.cpp +++ b/tslang/lib/TypeScript/LowerToLLVM.cpp @@ -4454,6 +4454,13 @@ struct GlobalOpLowering : public TsLlvmPattern globalOp.getInitializerRegion().walk(visitorAllOps); + // a branch in the initializer (a conditional, an optional widened into a union) has left + // it more than one block, which only a function can hold (#462) + if (!globalOp.getInitializerRegion().empty() && !globalOp.getInitializerRegion().hasOneBlock()) + { + createAsGlobalConstructor = true; + } + auto linkage = globalOp.getLinkage(); LLVM::GlobalOp llvmGlobalOp; if (createAsGlobalConstructor) diff --git a/tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp b/tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp index f2f4e559b..acf10bb3e 100644 --- a/tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp +++ b/tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp @@ -304,6 +304,77 @@ class OwnedReturnConsumptionPass return; } + // An optional widened into a union (`let g: C | null | undefined = mkU()`, #462) is a + // ts.If over the call made before it: each branch yields that call's value through + // views, or a value that holds nothing (the empty optional). The global takes the call's + // count, as it would take the call's straight. + if (auto ifOp = value.getDefiningOp()) + { + auto throughViews = [](mlir::Value yielded) { + while (auto *definingOp = yielded.getDefiningOp()) + { + if (auto castOp = mlir::dyn_cast(definingOp)) + { + yielded = castOp.getIn(); + } + else if (auto optionalValueOp = mlir::dyn_cast(definingOp)) + { + yielded = optionalValueOp.getIn(); + } + else if (auto createUnionInstanceOp = mlir::dyn_cast(definingOp)) + { + yielded = createUnionInstanceOp.getIn(); + } + else if (auto valueOp = mlir::dyn_cast(definingOp)) + { + yielded = valueOp.getIn(); + } + else + { + break; + } + } + + return yielded; + }; + + mlir::Value producer; + auto agrees = true; + for (auto ®ion : ifOp->getRegions()) + { + auto resultOp = region.empty() ? mlir_ts::ResultOp() + : mlir::dyn_cast(region.back().getTerminator()); + if (!resultOp || resultOp->getNumOperands() != 1) + { + agrees = false; + break; + } + + auto yielded = throughViews(resultOp->getOperand(0)); + auto *yieldedOp = yielded.getDefiningOp(); + if (yieldedOp && mlir::isa(yieldedOp)) + { + continue; + } + + if (producer && producer != yielded) + { + agrees = false; + break; + } + + producer = yielded; + } + + if (agrees && producer && producesOwnedResult(producer) && + !ifOp->isAncestor(producer.getDefiningOp())) + { + producer.getDefiningOp()->setAttr(OWNED_RESULT_CONSUMED_ATTR_NAME, + mlir::UnitAttr::get(&getContext())); + return; + } + } + // An object literal (`let o = { s: mk() }`) is built in a temporary slot and read out // whole: what its fields were given goes into the global with it (#461), nested // literals included. diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 0f53f727a..3e750c2e4 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -430,6 +430,7 @@ tslang_add_test(NAME test-compile-00-throw-f32 COMMAND test-runner "${PROJECT_SO tslang_add_test(NAME test-compile-00-throw-u32 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_u32.ts") tslang_add_test(NAME test-compile-00-cast-to-f32 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00cast_to_f32.ts") tslang_add_test(NAME test-compile-00-instanceof-namespace-narrowing COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00instanceof_namespace_narrowing.ts") +tslang_add_test(NAME test-compile-00-global-initializer-branches COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_initializer_branches.ts") tslang_add_test(NAME test-compile-00-using-nested-scopes COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00using_nested_scopes.ts") tslang_add_test(NAME test-compile-00-using-unwind-progress COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00using_unwind_progress.ts") tslang_add_test(NAME test-compile-00-break-continue-scope-exit COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00break_continue_scope_exit.ts") @@ -917,6 +918,7 @@ tslang_add_test(NAME test-jit-00-throw-f32 COMMAND test-runner -jit "${PROJECT_S tslang_add_test(NAME test-jit-00-throw-u32 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_u32.ts") tslang_add_test(NAME test-jit-00-cast-to-f32 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00cast_to_f32.ts") tslang_add_test(NAME test-jit-00-instanceof-namespace-narrowing COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00instanceof_namespace_narrowing.ts") +tslang_add_test(NAME test-jit-00-global-initializer-branches COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_initializer_branches.ts") tslang_add_test(NAME test-jit-00-using-nested-scopes COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00using_nested_scopes.ts") tslang_add_test(NAME test-jit-00-using-unwind-progress COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00using_unwind_progress.ts") tslang_add_test(NAME test-jit-00-break-continue-scope-exit COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00break_continue_scope_exit.ts") @@ -1779,6 +1781,7 @@ set(TSLANG_CORPUS 00instanceof.ts 00const_of_let.ts 00instanceof_namespace_narrowing.ts + 00global_initializer_branches.ts 00instanceof_union.ts 00nullable_to_any.ts 00ternary_new_or_null.ts diff --git a/tslang/test/tester/tests/00global_initializer_branches.ts b/tslang/test/tester/tests/00global_initializer_branches.ts new file mode 100644 index 000000000..4a5390aaa --- /dev/null +++ b/tslang/test/tester/tests/00global_initializer_branches.ts @@ -0,0 +1,42 @@ +// A module-level variable whose initializer branches: an optional widened into a union with null +// (#462), and a conditional expression. Lowered, the initializer had more than one block, which a +// global's region could not hold: "'ts.Global' op expects region #0 to have 0 or 1 blocks". +class Node { + v = 0; + tag = ""; +} + +function mk(v: number) { + const n = new Node(); + n.v = v; + n.tag = "t" + v; + return n; +} + +function mkU(): Node | undefined { + return mk(7); +} + +function none(): Node | undefined { + return undefined; +} + +function on() { + return true; +} + +let g: Node | null | undefined = mkU(); +let h: Node | null | undefined = none(); +let x = on() ? 1 : 2; +const label = on() ? "yes" : "no"; +let pick: Node | null = on() ? mk(3) : null; + +function main() { + assert(g !== undefined && g !== null && g.tag == "t7", "an optional widened into a union"); + assert(h === undefined, "an empty optional widened into a union"); + assert(x == 1, "a conditional number"); + assert(label == "yes", "a conditional string"); + assert(pick !== null && pick.v == 3, "a conditional object"); + + print("done."); +}