diff --git a/tslang/lib/TypeScript/MLIRGenFunctions.cpp b/tslang/lib/TypeScript/MLIRGenFunctions.cpp index 1967c9551..5367bd62a 100644 --- a/tslang/lib/TypeScript/MLIRGenFunctions.cpp +++ b/tslang/lib/TypeScript/MLIRGenFunctions.cpp @@ -2013,6 +2013,21 @@ namespace mlirgen // review capturing by ref. it should match storage type auto refValue = mcl.GetReferenceFromValue(location, varValue); + + // A copy is taken through the reference only when that reference is the variable's own + // storage. A `const` folded into a read (`const v = n.v`, `const v = G`) has none: the + // reference is where it was read from, and dereferenced now it gave the closure what the + // place holds by the time the closure is made - `n.v = d` in between, and `v` in the + // closure was `d`. The value itself is what the const names. + auto isOwnStorage = refValue && (refValue.getDefiningOp() || + refValue.getDefiningOp() || + refValue.getDefiningOp() || + isCapturedCellSlot(refValue)); + if (refValue && !capturedByRef && !isOwnStorage) + { + refValue = mlir::Value(); + } + if (refValue && !capturedByRef) { // the box holds a copy, exactly as it does for a value with no reference at all diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 983a30334..5e25fb538 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-closure-const-read COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00closure_const_read.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") @@ -992,6 +993,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-closure-const-read COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00closure_const_read.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") @@ -1968,6 +1970,7 @@ set(TSLANG_CORPUS 00const_global_read.ts 00global_argument_reassigned.ts 00global_argument_view.ts + 00closure_const_read.ts 00element_store_after_value.ts 00const_interface_field_read.ts 00argument_place_overwritten.ts diff --git a/tslang/test/tester/tests/00closure_const_read.ts b/tslang/test/tester/tests/00closure_const_read.ts new file mode 100644 index 000000000..accaa733c --- /dev/null +++ b/tslang/test/tester/tests/00closure_const_read.ts @@ -0,0 +1,45 @@ +// A closure over a `const` read out of a field, an element or a global sees +// the value the const was given, not what the place holds by the time the closure is made. Folded +// into the read, the const had no storage of its own, and the capture was taken through the +// place it was read from: `const v = n.v; n.v = d; const c = () => v.x;` gave `d.x` under gc and +// none (rc gives such a const storage, and was right). +class V { + constructor(public x: number) {} +} + +class N { + v = new V(3); + items: V[] = [new V(4)]; +} + +let G = new V(5); + +function field() { + const n = new N(); + const v = n.v; + n.v = new V(2); + const c = (): number => v.x; + return c(); +} + +function element() { + const n = new N(); + const v = n.items[0]; + n.items[0] = new V(2); + const c = (): number => v.x; + return c(); +} + +function global() { + const v = G; + G = new V(2); + const c = (): number => v.x; + return c(); +} + +function main() { + assert(field() == 3, "a closure over a const read out of a field"); + assert(element() == 4, "a closure over a const read out of an element"); + assert(global() == 5, "a closure over a const read out of a global"); + print("done."); +}