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
21 changes: 20 additions & 1 deletion tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,18 @@ class MLIRGenImpl
return varOp && varOp->hasAttr(OWNED_LOCAL_ATTR_NAME);
}

// A value read out of a field, through any casts: what `h.c` is in `const c = h.c`.
static bool readsField(mlir::Value value)
{
while (auto castOp = value ? value.getDefiningOp<mlir_ts::CastOp>() : mlir_ts::CastOp())
{
value = castOp.getIn();
}

auto loadOp = value ? value.getDefiningOp<mlir_ts::LoadOp>() : mlir_ts::LoadOp();
return loadOp && loadOp.getReference().getDefiningOp<mlir_ts::PropertyRefOp>();
}

// A record read out whole from the temporary slot an object literal is built in (one that
// owns nothing of its own), with a field that owns a block and that only the literal's
// receiver counts - a `let` does, with RetainSlot - so a `const` of it needs storage like a
Expand Down Expand Up @@ -1827,7 +1839,13 @@ class MLIRGenImpl
// So does a const of anything that owns a block, read out of a local variable's slot
// (`let a = new C(); const b = a;`, a parameter too): folded, it would be a read of
// that slot, holding no reference of its own, and `a = d` would free what `b` still
// reads (#454). A const of a const, a call or a field has no slot to lose it from.
// reads (#454). A const of a const or a call has no slot to lose it from.
//
// Under rc, so does a const read out of a field (`const c = h.c`): folded, `h.c = d` freed
// what `c` still read. Under own the folded read is a borrow, and a use after the overwrite
// is an error there. Not yet an element: a destructuring past the end of an array
// (`const [a, b, c] = pair`) reads whatever follows its data, and a count taken of that
// faults, as it does for a `let` today.
//
// And a const record whose fields own blocks (`const o = { s: p.child }`): the literal
// hands its fields over unretained - its receiver is the one that retains them, as a
Expand All @@ -1846,6 +1864,7 @@ class MLIRGenImpl
|| MLIRTypeHelper::isConstTupleHoldingArray(type)
|| ((isa<mlir_ts::ArrayType>(type) || MLIRTypeHelper::isSharedHandleType(type)
|| (readsLocalSlot(initial) && mth.ownsHeapMemory(location, type))
|| (compileOptions.isRefCounted() && readsField(initial) && mth.ownsHeapMemory(location, type))
|| (readsLiteralSlot(mth, location, initial) && isa<mlir_ts::TupleType>(type)
&& mth.ownsHeapMemory(location, type))
|| (initial && initial.getDefiningOp<mlir_ts::CreateTupleOp>() && isa<mlir_ts::TupleType>(type)
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 @@ -393,6 +393,7 @@ tslang_add_test(NAME test-compile-00-generator-temporaries COMMAND test-runner "
tslang_add_test(NAME test-compile-00-array-length-valid COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts")
tslang_add_test(NAME test-compile-00-string-view-copy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_view_copy.ts")
tslang_add_test(NAME test-compile-00-c-string-copy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00c_string_copy.ts")
tslang_add_test(NAME test-compile-00-const-field-read COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_field_read.ts")
tslang_add_test(NAME test-compile-00-array-data-string-alias COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_data_string_alias.ts")
tslang_add_test(NAME test-compile-00-arrays7 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array7.ts")
tslang_add_test(NAME test-compile-00-arrays8-tuple-spread COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array8_tuple_spread.ts")
Expand Down Expand Up @@ -960,6 +961,7 @@ tslang_add_test(NAME test-jit-00-generator-temporaries COMMAND test-runner -jit
tslang_add_test(NAME test-jit-00-array-length-valid COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts")
tslang_add_test(NAME test-jit-00-string-view-copy COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_view_copy.ts")
tslang_add_test(NAME test-jit-00-c-string-copy COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00c_string_copy.ts")
tslang_add_test(NAME test-jit-00-const-field-read COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_field_read.ts")
tslang_add_test(NAME test-jit-00-array-data-string-alias COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_data_string_alias.ts")
tslang_add_test(NAME test-jit-00-arrays7 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array7.ts")
tslang_add_test(NAME test-jit-00-arrays8-tuple-spread COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array8_tuple_spread.ts")
Expand Down Expand Up @@ -1917,6 +1919,7 @@ set(TSLANG_CORPUS
00array_length_valid.ts
00string_view_copy.ts
00c_string_copy.ts
00const_field_read.ts
00array_data_string_alias.ts
00array7.ts
00array8_tuple_spread.ts
Expand Down
64 changes: 64 additions & 0 deletions tslang/test/tester/tests/00const_field_read.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// A `const` read out of a field keeps what it read alive when the field is given a new value.
// Folded, the const was the read itself and held no reference of its own: under rc the overwrite
// freed the block it still read. Allocations of the same size reuse anything freed before each
// read.
class C {
constructor(public x: number) {}
}

class H {
c: C = new C(1);
n: C | null = new C(2);
}

class Junk {
a = -1;
b = -2;
}

function churn() {
let keep: Junk[] = [];
for (let i = 0; i < 1000; i++) keep.push(new Junk());
return keep.length;
}

function field(i: number) {
const h = new H();
h.c = new C(i);
const c = h.c;
h.c = new C(9);
churn();
return c.x == i && h.c.x == 9;
}

function nullableField(i: number) {
const h = new H();
h.n = new C(i);
const c = h.n;
h.n = null;
churn();
return c != null && c.x == i;
}

function readInFinally(h: H) {
const c = h.c;
let x = 0;
try {
h.c = new C(9);
return x;
} finally {
churn();
x = c.x;
assert(x == 1, "the read survives the field's overwrite through finally");
}
}

function main() {
for (let i = 0; i < 20; i++) {
assert(field(i), "a field read");
assert(nullableField(i), "a nullable field read");
}

readInFinally(new H());
print("done.");
}
Loading