Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<mlir_ts::LoadOp>())
{
if (auto literalSlot = literalLoad.getReference().getDefiningOp<mlir_ts::VariableOp>();
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
Expand All @@ -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<mlir_ts::PropertyRefOp>(user);
if (!fieldRef)
{
continue;
}

for (auto *fieldUser : fieldRef->getUsers())
{
auto storeOp = mlir::dyn_cast<mlir_ts::StoreOp>(fieldUser);
if (!storeOp || storeOp.getReference() != fieldRef.getResult())
{
continue;
}

auto stored = storeOp.getValue();
while (auto *definingOp = stored.getDefiningOp())
{
if (auto castOp = mlir::dyn_cast<mlir_ts::CastOp>(definingOp))
{
stored = castOp.getIn();
}
else if (auto optionalValueOp = mlir::dyn_cast<mlir_ts::OptionalValueOp>(definingOp))
{
stored = optionalValueOp.getIn();
}
else if (auto createUnionInstanceOp = mlir::dyn_cast<mlir_ts::CreateUnionInstanceOp>(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<mlir_ts::LoadOp>())
{
if (auto innerSlot = innerLoad.getReference().getDefiningOp<mlir_ts::VariableOp>();
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
Expand Down
3 changes: 3 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
43 changes: 43 additions & 0 deletions tslang/test/tester/tests/00global_object_literal.ts
Original file line number Diff line number Diff line change
@@ -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.");
}
Loading