diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 843071c2a..423af4d27 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -1888,8 +1888,8 @@ class MLIRGenImpl // // 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. 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 + // does an interface or a bound method made over such a read (`const i: I = h.v`, + // `const m = a[0].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 @@ -1910,7 +1910,8 @@ class MLIRGenImpl || ((isa(type) || MLIRTypeHelper::isSharedHandleType(type) || ((readsLocalSlot(initial) || viewsLocalSlot(initial)) && mth.ownsHeapMemory(location, type)) || (compileOptions.isRefCounted() - && (readsField(initial) || readsElement(initial) || readsField(viewedValue(initial))) + && (readsField(initial) || readsElement(initial) || readsField(viewedValue(initial)) + || readsElement(viewedValue(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 b6e8a0b50..0a5fc7678 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -392,6 +392,7 @@ tslang_add_test(NAME test-compile-00-shared-global-field-argument COMMAND test-r 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-view-of-element COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_view_of_element.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") @@ -967,6 +968,7 @@ tslang_add_test(NAME test-jit-00-shared-global-field-argument COMMAND test-runne 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-view-of-element COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_view_of_element.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") @@ -1932,6 +1934,7 @@ set(TSLANG_CORPUS 00narrowed_assign_other_member.ts 00bound_method_count.ts 00const_view_of_field.ts + 00const_view_of_element.ts 00const_record_owned_fields.ts 00generator_temporaries.ts 00array_length_valid.ts diff --git a/tslang/test/tester/tests/00const_view_of_element.ts b/tslang/test/tester/tests/00const_view_of_element.ts new file mode 100644 index 000000000..1880ad46b --- /dev/null +++ b/tslang/test/tester/tests/00const_view_of_element.ts @@ -0,0 +1,57 @@ +// A `const` interface or bound method made over an element read keeps the object alive when the +// element 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 `arr[0] = 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; + } +} + +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 arr: V[] = [mkv(n), mkv(0)]; + const m = arr[0].get; + arr[0] = mkv(n + 1); + churn(); + return m() == n && arr[0].w.x == n + 1; +} + +function interfaceView(n: number) { + const arr: V[] = [mkv(0), mkv(n)]; + const i: I = arr[1]; + arr[1] = mkv(n + 1); + churn(); + return i.get() == n && arr[1].w.x == n + 1; +} + +function main() { + for (let n = 0; n < 20; n++) { + assert(boundMethod(n), "a const bound method over an element"); + assert(interfaceView(n), "a const interface over an element"); + } + + print("done."); +}