diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 0ed51d25e..daf2c9e35 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -1725,7 +1725,8 @@ class MLIRGenImpl return readsLocalSlot(viewedValue(value)); } - // A value read out of a field, through any casts: what `h.c` is in `const c = h.c`. + // A value read out of a field, through any casts: what `h.c` is in `const c = h.c`, seen + // through an interface too (`const c = ix.c`), and what a handle holds (`const v = s.value`). static bool readsField(mlir::Value value) { while (auto castOp = value ? value.getDefiningOp() : mlir_ts::CastOp()) @@ -1734,7 +1735,8 @@ class MLIRGenImpl } auto loadOp = value ? value.getDefiningOp() : mlir_ts::LoadOp(); - return loadOp && loadOp.getReference().getDefiningOp(); + return loadOp && mlir::isa_and_nonnull(loadOp.getReference().getDefiningOp()); } // A value read out of a global variable, through any casts: what `G` is in `const n = G`. @@ -1898,7 +1900,8 @@ class MLIRGenImpl // 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`), an element (`const c = + // Under rc, so does a const read out of a field (`const c = h.c`, also through an interface, + // `const c = ix.c`, and a handle's value, `const c = s.value`), an element (`const c = // a[0]`, `const [c] = a`) or a global (`const c = G`): folded, `h.c = d`, `a[0] = d` or // `G = d` - here or in a call - freed what `c` still read. So 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 diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index b9d514a29..f1bc96afa 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -405,6 +405,7 @@ tslang_add_test(NAME test-compile-00-const-element-read COMMAND test-runner "${P tslang_add_test(NAME test-compile-00-const-global-read COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_global_read.ts") tslang_add_test(NAME test-compile-00-global-argument-reassigned COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_argument_reassigned.ts") tslang_add_test(NAME test-compile-00-global-argument-view COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_argument_view.ts") +tslang_add_test(NAME test-compile-00-const-interface-field-read COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_interface_field_read.ts") tslang_add_test(NAME test-compile-00-argument-place-overwritten COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00argument_place_overwritten.ts") tslang_add_test(NAME test-compile-00-temporary-read-in-finally COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00temporary_read_in_finally.ts") tslang_add_test(NAME test-compile-00-unowned-local-store COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00unowned_local_store.ts") @@ -988,6 +989,7 @@ tslang_add_test(NAME test-jit-00-const-element-read COMMAND test-runner -jit "${ tslang_add_test(NAME test-jit-00-const-global-read COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_global_read.ts") tslang_add_test(NAME test-jit-00-global-argument-reassigned COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_argument_reassigned.ts") tslang_add_test(NAME test-jit-00-global-argument-view COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_argument_view.ts") +tslang_add_test(NAME test-jit-00-const-interface-field-read COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_interface_field_read.ts") tslang_add_test(NAME test-jit-00-argument-place-overwritten COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00argument_place_overwritten.ts") tslang_add_test(NAME test-jit-00-temporary-read-in-finally COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00temporary_read_in_finally.ts") tslang_add_test(NAME test-jit-00-unowned-local-store COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00unowned_local_store.ts") @@ -1961,6 +1963,7 @@ set(TSLANG_CORPUS 00const_global_read.ts 00global_argument_reassigned.ts 00global_argument_view.ts + 00const_interface_field_read.ts 00argument_place_overwritten.ts 00temporary_read_in_finally.ts 00unowned_local_store.ts diff --git a/tslang/test/tester/tests/00const_interface_field_read.ts b/tslang/test/tester/tests/00const_interface_field_read.ts new file mode 100644 index 000000000..bda1eee0d --- /dev/null +++ b/tslang/test/tester/tests/00const_interface_field_read.ts @@ -0,0 +1,83 @@ +// A `const` read out of a field through an interface (`const v = ix.v`), or out of what a Shared +// handle holds (`const v = s.value`), keeps what it read alive when that 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 (00const_field_read.ts is the class field case). +// Allocations of the same size reuse anything freed before each read. +class V { + tag = ""; + constructor(public x: number) { + this.tag = "v" + x; + } +} + +class N { + constructor(public v: V) {} +} + +interface IX { + v: V; +} + +function churn() { + let keep: V[] = []; + for (let i = 0; i < 1000; i++) keep.push(new V(999)); + return keep.length; +} + +let GS = new Shared(new V(1)); + +function interfaceOfLiteral(i: number) { + const ix: IX = { v: new V(0) }; + ix.v = new V(i); + const v = ix.v; + ix.v = new V(9); + churn(); + assert(v.x == i && v.tag == "v" + i, "a const read through an interface over an object literal"); +} + +function interfaceOfClass(i: number) { + const ix: IX = new N(new V(0)); + ix.v = new V(i); + const v = ix.v; + ix.v = new V(9); + churn(); + assert(v.x == i && v.tag == "v" + i, "a const read through an interface over a class"); +} + +function interfaceInLoop() { + let ix: IX = { v: new V(1) }; + for (let i = 0; i < 3; i++) { + const v = ix.v; + ix.v = new V(i + 10); + churn(); + assert(v.x == (i == 0 ? 1 : i + 9), "a const read through an interface, in a loop"); + } +} + +function sharedValue(i: number) { + GS.value = new V(i); + const v = GS.value; + GS.value = new V(9); + churn(); + assert(v.x == i && v.tag == "v" + i, "a const read of a handle's value"); +} + +function sharedHandleReplaced(i: number) { + GS = new Shared(new V(i)); + const v = GS.value; + GS = new Shared(new V(9)); + churn(); + assert(v.x == i && v.tag == "v" + i, "a const read of a handle's value, the handle replaced"); +} + +function main() { + for (let i = 2; i < 5; i++) { + interfaceOfLiteral(i); + interfaceOfClass(i); + sharedValue(i); + sharedHandleReplaced(i); + } + + interfaceInLoop(); + print("done."); +}