diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index daf2c9e35..08a239447 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -6482,6 +6482,16 @@ class MLIRGenImpl return mlir::failure(); } + // An element's address is taken anew once the value is ready. The one the left side was + // read through was taken before the right side ran, and the right side may grow the array: + // `a[0] = f()` with `f` pushing to `a` moved its elements to a new block, and the store went + // into the one given back (a value lost under every model, a freed block written to under + // rc). The array and the index are the ones the left side evaluated, as JavaScript has it. + if (auto elementRefOp = storeReference.getDefiningOp()) + { + storeReference = builder.clone(*elementRefOp)->getResult(0); + } + // Overwriting owning storage hands the count over: the incoming value gains an // owner and the outgoing one loses it. Retaining first is what makes `x = x` safe - // releasing first could drop the last reference and free the value about to be diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index a52ac3044..983a30334 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -406,6 +406,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-element-store-after-value COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00element_store_after_value.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") @@ -991,6 +992,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-element-store-after-value COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00element_store_after_value.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") @@ -1966,6 +1968,7 @@ set(TSLANG_CORPUS 00const_global_read.ts 00global_argument_reassigned.ts 00global_argument_view.ts + 00element_store_after_value.ts 00const_interface_field_read.ts 00argument_place_overwritten.ts 00temporary_read_in_finally.ts diff --git a/tslang/test/tester/tests/00element_store_after_value.ts b/tslang/test/tester/tests/00element_store_after_value.ts new file mode 100644 index 000000000..10129b36d --- /dev/null +++ b/tslang/test/tester/tests/00element_store_after_value.ts @@ -0,0 +1,67 @@ +// `a[i] = f()` stores into the array once `f()` has run, even when `f` grows the array. The +// element's address was taken before the right side ran, and growing moved the elements to a new +// block: the store went into the one given back. The value was lost under every model, and under +// rc a freed block was written to. The array and the index are still the ones the left side +// evaluated first, as in JavaScript. +class V { + constructor(public x: number) {} +} + +class H { + a: number[] = [1]; + items: V[] = [new V(1)]; +} + +function growNumbers(a: number[]) { + for (let i = 0; i < 16; i++) a.push(i); + return 7; +} + +function growItems(items: V[]) { + for (let i = 0; i < 16; i++) items.push(new V(100 + i)); + return new V(8); +} + +function numbers() { + const a: number[] = [1]; + a[0] = growNumbers(a); + return a.length == 17 && a[0] == 7 && a[1] == 0; +} + +function objects() { + const items = [new V(1)]; + items[0] = growItems(items); + return items.length == 17 && items[0].x == 8 && items[16].x == 115; +} + +function compound() { + const a: number[] = [1]; + a[0] += growNumbers(a); + return a[0] == 8; +} + +function field() { + const h = new H(); + h.a[0] = growNumbers(h.a); + h.items[0] = growItems(h.items); + return h.a[0] == 7 && h.items[0].x == 8; +} + +function otherArray() { + let a: number[] = [1, 2]; + const old = a; + a[1] = (a = [5], 9); + return old[1] == 9 && a.length == 1 && a[0] == 5; +} + +function main() { + for (let i = 0; i < 3; i++) { + assert(numbers(), "a number stored after the call grows the array"); + assert(objects(), "an object stored after the call grows the array"); + assert(compound(), "a compound assignment after the call grows the array"); + assert(field(), "an element of a field stored after the call grows the array"); + assert(otherArray(), "the left side's array, though the right side assigns the variable"); + } + + print("done."); +}