diff --git a/tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp b/tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp index c7b183445..f2f4e559b 100644 --- a/tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp +++ b/tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp @@ -304,6 +304,19 @@ class OwnedReturnConsumptionPass 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. + if (auto literalLoad = value.getDefiningOp()) + { + if (auto literalSlot = literalLoad.getReference().getDefiningOp(); + literalSlot && !literalSlot->hasAttr(OWNED_LOCAL_ATTR_NAME)) + { + claimStoredIntoLiteral(literalSlot); + return; + } + } + // A field or element read (`holder.child`, `new P().child`, `mkP().child`, // `mkArr()[0]`) carries no reference: the object or array keeps its count, and a // temporary one gives it back at the end of the region. The global takes a count of its @@ -321,6 +334,65 @@ class OwnedReturnConsumptionPass }); } + // Each owned producer stored into a field of an object literal's temporary slot, through the + // views of claimGlobalInitializers, is taken over by whoever takes the literal; a field that is + // itself a literal read out of its own temporary slot is followed into. + void claimStoredIntoLiteral(mlir_ts::VariableOp literalSlot) + { + for (auto *user : literalSlot->getUsers()) + { + auto fieldRef = mlir::dyn_cast(user); + if (!fieldRef) + { + continue; + } + + for (auto *fieldUser : fieldRef->getUsers()) + { + auto storeOp = mlir::dyn_cast(fieldUser); + if (!storeOp || storeOp.getReference() != fieldRef.getResult()) + { + continue; + } + + auto stored = storeOp.getValue(); + while (auto *definingOp = stored.getDefiningOp()) + { + if (auto castOp = mlir::dyn_cast(definingOp)) + { + stored = castOp.getIn(); + } + else if (auto optionalValueOp = mlir::dyn_cast(definingOp)) + { + stored = optionalValueOp.getIn(); + } + else if (auto createUnionInstanceOp = mlir::dyn_cast(definingOp)) + { + stored = createUnionInstanceOp.getIn(); + } + else + { + break; + } + } + + if (producesOwnedResult(stored)) + { + stored.getDefiningOp()->setAttr(OWNED_RESULT_CONSUMED_ATTR_NAME, + mlir::UnitAttr::get(&getContext())); + } + else if (auto innerLoad = stored.getDefiningOp()) + { + if (auto innerSlot = innerLoad.getReference().getDefiningOp(); + innerSlot && !innerSlot->hasAttr(OWNED_LOCAL_ATTR_NAME)) + { + claimStoredIntoLiteral(innerSlot); + } + } + } + } + } + // A conditional expression whose branches disagree about ownership. // // `cond ? a : f()` builds a `ts.If` with a result, and the two branches hand back references diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 8dfd1022b..0f53f727a 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -467,6 +467,7 @@ tslang_add_test(NAME test-compile-00-void COMMAND test-runner "${PROJECT_SOURCE_ tslang_add_test(NAME test-compile-00-in COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00in.ts") tslang_add_test(NAME test-compile-00-in-method-names COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00in_method_names.ts") tslang_add_test(NAME test-compile-00-class-structural-extends COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_structural_extends.ts") +tslang_add_test(NAME test-compile-00-global-object-literal COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_object_literal.ts") tslang_add_test(NAME test-compile-00-instanceof COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00instanceof.ts") tslang_add_test(NAME test-compile-00-const-of-let COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_of_let.ts") tslang_add_test(NAME test-compile-00-instanceof-union COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00instanceof_union.ts") @@ -954,6 +955,7 @@ tslang_add_test(NAME test-jit-00-void COMMAND test-runner -jit "${PROJECT_SOURCE tslang_add_test(NAME test-jit-00-in COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00in.ts") tslang_add_test(NAME test-jit-00-in-method-names COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00in_method_names.ts") tslang_add_test(NAME test-jit-00-class-structural-extends COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_structural_extends.ts") +tslang_add_test(NAME test-jit-00-global-object-literal COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_object_literal.ts") tslang_add_test(NAME test-jit-00-instanceof COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00instanceof.ts") tslang_add_test(NAME test-jit-00-const-of-let COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_of_let.ts") tslang_add_test(NAME test-jit-00-instanceof-union COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00instanceof_union.ts") @@ -1771,6 +1773,7 @@ set(TSLANG_CORPUS 00if_return.ts 00in_method_names.ts 00in.ts + 00global_object_literal.ts 00infer.ts 00global_union_from_call.ts 00instanceof.ts diff --git a/tslang/test/tester/tests/00global_object_literal.ts b/tslang/test/tester/tests/00global_object_literal.ts new file mode 100644 index 000000000..28d7e2bda --- /dev/null +++ b/tslang/test/tester/tests/00global_object_literal.ts @@ -0,0 +1,43 @@ +// A module-level object literal keeps what its fields were given (#461). Under rc the call's +// reference was given back at the end of the global's initializer, and nothing else held one, so +// the field pointed at freed memory. Each field is read after allocations of the same size have +// reused anything freed. +class Node { + v = 0; + name = ""; +} + +function mk(name: string) { + const n = new Node(); + n.name = name; + return n; +} + +function churn() { + let keep: Node[] = []; + for (let i = 0; i < 1000; i++) { + const n = new Node(); + n.v = -i; + n.name = "junk" + i; + keep.push(n); + } + + return keep.length; +} + +let o = { s: mk("made" + 1), n: 7 }; +const p = { a: mk("first" + 2), b: mk("second" + 3) }; +let t = { inner: { s: mk("inner" + 4) } }; + +function main() { + assert(churn() == 1000); + assert(o.s.name == "made1" && o.n == 7, "a global literal keeps its field's object"); + assert(p.a.name == "first2" && p.b.name == "second3", "a const global literal keeps both fields"); + assert(t.inner.s.name == "inner4", "a nested global literal keeps its object"); + + o = { s: mk("next" + 5), n: 8 }; + assert(churn() == 1000); + assert(o.s.name == "next5" && o.n == 8, "a reassigned global literal keeps the new object"); + + print("done."); +}