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
24 changes: 22 additions & 2 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,24 @@ 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)
{
auto *definingOp = value ? value.getDefiningOp() : nullptr;
if (auto newInterfaceOp = dyn_cast_or_null<mlir_ts::NewInterfaceOp>(definingOp))
{
return readsLocalSlot(newInterfaceOp.getThisVal());
}

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

return false;
}

// 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)
{
Expand Down Expand Up @@ -1839,7 +1857,9 @@ 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 or a call has no slot to lose it from.
// reads (#454). A const of a const or a call has no slot to lose it from. So does an
// interface or a bound method made over such a read (`const i: I = a`, `const m = a.get`):
// it holds the block as `b` would.
//
// 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
Expand All @@ -1863,7 +1883,7 @@ class MLIRGenImpl
needsIdentityStorage = mth.hasBoundMethodField(type) || isa<mlir_ts::ConstArrayType>(type)
|| MLIRTypeHelper::isConstTupleHoldingArray(type)
|| ((isa<mlir_ts::ArrayType>(type) || MLIRTypeHelper::isSharedHandleType(type)
|| (readsLocalSlot(initial) && mth.ownsHeapMemory(location, type))
|| ((readsLocalSlot(initial) || viewsLocalSlot(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))
Expand Down
8 changes: 8 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ tslang_add_test(NAME test-compile-00-narrowed-assign-other-member COMMAND test-r
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")
tslang_add_test(NAME test-compile-00-const-interface-of-local COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_interface_of_local.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-global-copy-counted COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_copy_counted.ts")
tslang_add_test(NAME test-compile-00-c-string-copy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00c_string_copy.ts")
Expand Down Expand Up @@ -960,6 +961,7 @@ tslang_add_test(NAME test-jit-00-narrowed-assign-other-member COMMAND test-runne
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")
tslang_add_test(NAME test-jit-00-const-interface-of-local COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_interface_of_local.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-global-copy-counted COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_copy_counted.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")
Expand Down Expand Up @@ -1919,6 +1921,7 @@ set(TSLANG_CORPUS
00const_record_owned_fields.ts
00generator_temporaries.ts
00array_length_valid.ts
00const_interface_of_local.ts
00string_view_copy.ts
00global_copy_counted.ts
00c_string_copy.ts
Expand Down Expand Up @@ -3558,6 +3561,10 @@ add_test(NAME test-rc-generator-temporaries
tslang_add_test(NAME test-jit-own-00-generator-temporaries COMMAND test-runner -jit -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_temporaries.ts")
tslang_add_test(NAME test-compile-own-00-generator-temporaries COMMAND test-runner -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_temporaries.ts")

# A const interface made over a local keeps the object when the local is assigned again, under own too.
tslang_add_test(NAME test-jit-own-00-const-interface-of-local COMMAND test-runner -jit -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_interface_of_local.ts")
tslang_add_test(NAME test-compile-own-00-const-interface-of-local COMMAND test-runner -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_interface_of_local.ts")

# Shared<T> (spec 23): counted handles. These are not in the own_test list, whose
# test-own-no-counting check forbids __tslang_inc_ref: a handle is counted under own by design.
# own_shared_user_named: a program's own class named Shared wins over the built-in (spec 23.1).
Expand Down Expand Up @@ -3633,6 +3640,7 @@ set(own_error_cases
"own_err_move_some_paths_field|is moved here on some paths only"
"own_err_borrow_and_move|is moved here while .* borrows it"
"own_err_borrow_bound_method|borrows .* but is used here after .* is released or overwritten"
"own_err_const_bound_method_reassigned|takes a second reference"
"own_err_borrow_catch_local|borrows .* but is used here after .* is released or overwritten"
"own_err_borrow_finally_local|borrows .* but is used here after .* is released or overwritten"
"own_err_borrow_derived_ref|borrows .* but is used here after .* is released or overwritten"
Expand Down
23 changes: 23 additions & 0 deletions tslang/test/tester/own/own_err_const_bound_method_reassigned.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// -mm=own rejects: `m` is `v.get` bound to the object `v` holds, and `v = mkv(2)` frees that
// object before `m()` runs. The method keeps no count of its own.
class W {
constructor(public x: number) {}
}

class V {
constructor(public w: W) {}
get() {
return this.w.x;
}
}

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

function main() {
let v = mkv(1);
const m = v.get;
v = mkv(2);
print(m() + v.w.x);
}
44 changes: 44 additions & 0 deletions tslang/test/tester/tests/00const_interface_of_local.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// A `const` interface made over a local (`const i: I = v`) keeps the object alive when the local
// is given a new value. Folded, the interface was a view of the read and held no reference of its
// own: under rc and own the assignment freed the object it still showed. Allocations of the same
// size reuse anything freed before each read.
class W {
constructor(public x: number) {}
}

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

interface I {
w: W;
}

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

function churn(): number {
let s = 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 view(n: number) {
let v = mkv(n);
const i: I = v;
v = mkv(n + 1);
churn();
return i.w.x == n && v.w.x == n + 1;
}

function main() {
for (let n = 0; n < 20; n++) {
assert(view(n), "an interface over a local outlives the local's assignment");
}

print("done.");
}
Loading