diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 3c6be6682..b36188534 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -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(definingOp)) + { + return readsLocalSlot(newInterfaceOp.getThisVal()); + } + + if (isa_and_nonnull(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) { @@ -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 @@ -1863,7 +1883,7 @@ class MLIRGenImpl needsIdentityStorage = mth.hasBoundMethodField(type) || isa(type) || MLIRTypeHelper::isConstTupleHoldingArray(type) || ((isa(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(type) && mth.ownsHeapMemory(location, type)) diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 2f61af6f5..36741205b 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -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") @@ -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") @@ -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 @@ -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 (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). @@ -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" diff --git a/tslang/test/tester/own/own_err_const_bound_method_reassigned.ts b/tslang/test/tester/own/own_err_const_bound_method_reassigned.ts new file mode 100644 index 000000000..cf11d7631 --- /dev/null +++ b/tslang/test/tester/own/own_err_const_bound_method_reassigned.ts @@ -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); +} diff --git a/tslang/test/tester/tests/00const_interface_of_local.ts b/tslang/test/tester/tests/00const_interface_of_local.ts new file mode 100644 index 000000000..09e52148f --- /dev/null +++ b/tslang/test/tester/tests/00const_interface_of_local.ts @@ -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."); +}