diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 08a239447..ce82cca98 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -1702,9 +1702,15 @@ class MLIRGenImpl } // The value an interface or a bound method is made over: `v` seen through `I` in - // `const i: I = v`, or `this` of `const m = v.get`. + // `const i: I = v`, or `this` of `const m = v.get`, through any casts: a declared type + // (`const m: () => number = v.get`) casts the bound method to a hybrid function. static mlir::Value viewedValue(mlir::Value value) { + while (auto castOp = value ? value.getDefiningOp() : mlir_ts::CastOp()) + { + value = castOp.getIn(); + } + auto *definingOp = value ? value.getDefiningOp() : nullptr; if (auto newInterfaceOp = dyn_cast_or_null(definingOp)) { diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 798e0ba0c..03b4efc40 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -411,6 +411,7 @@ tslang_add_test(NAME test-compile-00-closure-const-read COMMAND test-runner "${P 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-const-bound-method-declared-type COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_bound_method_declared_type.ts") tslang_add_test(NAME test-compile-00-argument-place-aliased COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00argument_place_aliased.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") @@ -1001,6 +1002,7 @@ tslang_add_test(NAME test-jit-00-closure-const-read COMMAND test-runner -jit "${ 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-const-bound-method-declared-type COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_bound_method_declared_type.ts") tslang_add_test(NAME test-jit-00-argument-place-aliased COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00argument_place_aliased.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") @@ -1981,6 +1983,7 @@ set(TSLANG_CORPUS 00element_store_after_value.ts 00const_interface_field_read.ts 00argument_place_overwritten.ts + 00const_bound_method_declared_type.ts 00argument_place_aliased.ts 00temporary_read_in_finally.ts 00unowned_local_store.ts diff --git a/tslang/test/tester/tests/00const_bound_method_declared_type.ts b/tslang/test/tester/tests/00const_bound_method_declared_type.ts new file mode 100644 index 000000000..d3cc51046 --- /dev/null +++ b/tslang/test/tester/tests/00const_bound_method_declared_type.ts @@ -0,0 +1,115 @@ +// A const bound method with a declared type (`const c: () => number = G.items[0].get`) keeps its +// object alive, as one without does. Under rc a const made over a read of a field, an element or a +// global holds a count of its own; the declared type casts the bound method to a hybrid function, +// and that cast hid the read, so the const was folded with no count, and `G = mk()` freed the +// object `c()` still ran on. Each read follows allocations of the same size, so a freed block is +// reused. +class W { + constructor(public x: number) {} +} + +class V { + x: number; + w: W; + a: number[]; + + constructor(x: number) { + this.x = x; + this.w = new W(x); + this.a = [x]; + } + + get(): number { + return this.x; + } +} + +class N { + v: V; + items: V[]; + + constructor(x: number) { + this.v = new V(x); + this.items = [new V(x)]; + } +} + +@noinline function mk(i: number): N { + return new N(i); +} + +function churn() { + const junk: V[] = []; + for (let i = 0; i < 64; i++) { + junk.push(new V(777)); + } +} + +let G: N = mk(2); + +interface IN { + v: V; + items: V[]; +} + +@noinline function mv(x: number): V { + return new V(x); +} + +function take(n: N, i: IN) { + return i.items[0].x; +} + +function fieldOfGlobal(x: number) { + G = mk(x); + const c: () => number = G.v.get; + G = mk(0); + churn(); + return c(); +} + +function fieldOfLocal(x: number) { + let n = mk(x); + const c: () => number = n.v.get; + n = mk(0); + churn(); + return c(); +} + +function local(x: number) { + let v = new V(x); + const c: () => number = v.get; + v = new V(0); + churn(); + return c(); +} + +function inLoop(x: number) { + G = mk(x); + const c: () => number = G.items[0].get; + for (let i = 0; i < 2; i++) { + G = mk(i); + churn(); + } + + return c(); +} + +function main() { + // an element of a global, the fuzzer's shape + const c: () => number = G.items[0].get; + let s: number = 0; + for (let i = 0; i < 1; i++) { + G = mk(4); + s += take(mk(5), { v: mv(i + 5), items: [mv(2)] }); + } + + assert(c() == 2, "an element of a global"); + assert(s == 2, "the call's own element"); + + assert(fieldOfGlobal(4) == 4, "a field of a global"); + assert(fieldOfLocal(5) == 5, "a field of a local"); + assert(local(6) == 6, "a local"); + assert(inLoop(7) == 7, "the global reassigned in a loop"); + print("done."); +}