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

// An interface or a bound method made over a value read out of such a local: `v` seen
// through `I` in `const i: I = v`, or `this` of `const m = v.get`.
static bool viewsLocalSlot(mlir::Value value)
// The value an interface or a bound method is made over: `v` seen through `I` in
// `const i: I = v`, or `this` of `const m = v.get`.
static mlir::Value viewedValue(mlir::Value value)
{
auto *definingOp = value ? value.getDefiningOp() : nullptr;
if (auto newInterfaceOp = dyn_cast_or_null<mlir_ts::NewInterfaceOp>(definingOp))
{
return readsLocalSlot(newInterfaceOp.getThisVal());
return newInterfaceOp.getThisVal();
}

if (isa_and_nonnull<mlir_ts::ThisSymbolRefOp, mlir_ts::ThisVirtualSymbolRefOp, mlir_ts::CreateBoundFunctionOp>(definingOp))
{
return readsLocalSlot(definingOp->getOperand(0));
return definingOp->getOperand(0);
}

return false;
return mlir::Value();
}

// An interface or a bound method made over a value read out of such a local.
static bool viewsLocalSlot(mlir::Value value)
{
return readsLocalSlot(viewedValue(value));
}

// A value read out of a field, through any casts: what `h.c` is in `const c = h.c`.
Expand Down Expand Up @@ -1881,8 +1887,10 @@ class MLIRGenImpl
// it holds the block as `b` would.
//
// Under rc, so does a const read out of a field (`const c = h.c`) or an element (`const c =
// a[0]`, `const [c] = a`): folded, `h.c = d` or `a[0] = d` freed what `c` still read. Under
// own the folded read is a borrow, and a use after the overwrite is an error there.
// a[0]`, `const [c] = a`): folded, `h.c = d` or `a[0] = d` freed what `c` still read. So
// does an interface or a bound method made over a field read (`const i: I = h.v`,
// `const m = h.v.get`), which holds the block as `c` would. Under own the folded read is a
// borrow, and a use after the overwrite is an error there.
//
// 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 @@ -1901,7 +1909,8 @@ class MLIRGenImpl
|| MLIRTypeHelper::isConstTupleHoldingArray(type)
|| ((isa<mlir_ts::ArrayType>(type) || MLIRTypeHelper::isSharedHandleType(type)
|| ((readsLocalSlot(initial) || viewsLocalSlot(initial)) && mth.ownsHeapMemory(location, type))
|| (compileOptions.isRefCounted() && (readsField(initial) || readsElement(initial))
|| (compileOptions.isRefCounted()
&& (readsField(initial) || readsElement(initial) || readsField(viewedValue(initial)))
&& mth.ownsHeapMemory(location, type))
|| (readsLiteralSlot(mth, location, initial) && isa<mlir_ts::TupleType>(type)
&& mth.ownsHeapMemory(location, 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 @@ -390,6 +390,7 @@ tslang_add_test(NAME test-compile-00-parse-number-range COMMAND test-runner "${P
tslang_add_test(NAME test-compile-00-global-argument-held COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_argument_held.ts")
tslang_add_test(NAME test-compile-00-narrowed-assign-other-member COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00narrowed_assign_other_member.ts")
tslang_add_test(NAME test-compile-00-bound-method-count COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00bound_method_count.ts")
tslang_add_test(NAME test-compile-00-const-view-of-field COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_view_of_field.ts")
tslang_add_test(NAME test-compile-00-const-record-owned-fields COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_record_owned_fields.ts")
tslang_add_test(NAME test-compile-00-generator-temporaries COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_temporaries.ts")
tslang_add_test(NAME test-compile-00-array-length-valid COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts")
Expand Down Expand Up @@ -963,6 +964,7 @@ tslang_add_test(NAME test-jit-00-parse-number-range COMMAND test-runner -jit "${
tslang_add_test(NAME test-jit-00-global-argument-held COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_argument_held.ts")
tslang_add_test(NAME test-jit-00-narrowed-assign-other-member COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00narrowed_assign_other_member.ts")
tslang_add_test(NAME test-jit-00-bound-method-count COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00bound_method_count.ts")
tslang_add_test(NAME test-jit-00-const-view-of-field COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_view_of_field.ts")
tslang_add_test(NAME test-jit-00-const-record-owned-fields COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_record_owned_fields.ts")
tslang_add_test(NAME test-jit-00-generator-temporaries COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_temporaries.ts")
tslang_add_test(NAME test-jit-00-array-length-valid COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts")
Expand Down Expand Up @@ -1926,6 +1928,7 @@ set(TSLANG_CORPUS
00global_argument_held.ts
00narrowed_assign_other_member.ts
00bound_method_count.ts
00const_view_of_field.ts
00const_record_owned_fields.ts
00generator_temporaries.ts
00array_length_valid.ts
Expand Down
63 changes: 63 additions & 0 deletions tslang/test/tester/tests/00const_view_of_field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// A `const` interface or bound method made over a field read keeps the object alive when the field
// is given a new value, as a `let` does. Folded, the const was the view itself and held no
// reference of its own: under rc `h.v = mkv(..)` freed the object `m()` and `i.get()` then ran on.
// Each call follows allocations of the same size, so a freed object is reused.
interface I {
get(): number;
}

class W {
constructor(public x: number) {}
}

class V {
items: number[] = [1, 2, 3];
constructor(public w: W) {}

get() {
return this.w.x;
}
}

class H {
v: V = mkv(0);
}

function mkv(n: number) {
return new V(new W(n));
}

function churn(): number {
let s: number = 0;
const a: V[] = [];
for (let i = 0; i < 64; i++) a.push(mkv(1000 + i));
for (const e of a) s += e.w.x;
return s;
}

function boundMethod(n: number) {
const h = new H();
h.v = mkv(n);
const m = h.v.get;
h.v = mkv(n + 1);
churn();
return m() == n && h.v.w.x == n + 1;
}

function interfaceView(n: number) {
const h = new H();
h.v = mkv(n);
const i: I = h.v;
h.v = mkv(n + 1);
churn();
return i.get() == n && h.v.w.x == n + 1;
}

function main() {
for (let n = 0; n < 20; n++) {
assert(boundMethod(n), "a const bound method over a field");
assert(interfaceView(n), "a const interface over a field");
}

print("done.");
}
Loading