diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 245d63b85..db888dc58 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -2123,6 +2123,26 @@ class MLIRGenImpl // save value auto address = builder.create( location, mlir_ts::RefType::get(variableDeclarationInfo.type), variableDeclarationInfo.fullName, mlir::IntegerAttr()); + + // A global is a root and owns what it holds, so its declaration hands the count over + // as an assignment to it does (isOwnedGlobalSlot): `const t = new C()` in top-level + // code stored the instance, and ยง9.30 then gave its reference back at the end of the + // block, leaving the global pointing at a freed block. A `var` met again in a loop + // gives up what it held before. + if (isOwningSlot(location, address)) + { + if (producesOwnedReference(variableDeclarationInfo.initial)) + { + consumeOwnedReference(variableDeclarationInfo.initial); + } + else + { + builder.create(location, variableDeclarationInfo.initial); + } + + builder.create(location, address); + } + auto storeOp = builder.create(location, variableDeclarationInfo.initial, address); if (variableDeclarationInfo.varClass.atomic) { diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index b0095354c..3861d97e1 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -2564,7 +2564,7 @@ foreach(own_test own_fresh_string own_fresh_array own_return_new own_try_local o own_call_no_drops own_getter_borrow own_param_kept own_any_box own_string_length_borrow own_closure_borrow own_closure_escape own_generator own_interface_view - own_async own_record_return own_generator_borrow) + own_async own_record_return own_generator_borrow own_global_script) tslang_add_test(NAME test-jit-own-${own_test} COMMAND test-runner -jit -mm=own "${PROJECT_SOURCE_DIR}/test/tester/own/${own_test}.ts") tslang_add_test(NAME test-compile-own-${own_test} COMMAND test-runner -mm=own "${PROJECT_SOURCE_DIR}/test/tester/own/${own_test}.ts") # The promise: no counting at all in what own emits. @@ -2576,6 +2576,12 @@ foreach(own_test own_fresh_string own_fresh_array own_return_new own_try_local o FAIL_REGULAR_EXPRESSION "__tslang_inc_ref|__tslang_dec_ref|error:|Stack dump") endforeach() +# A global declared in top-level code owns what it is given, under every model that counts. +foreach(global_mm rc none gc) + tslang_add_test(NAME test-jit-${global_mm}-own-global-script COMMAND test-runner -jit -mm=${global_mm} "${PROJECT_SOURCE_DIR}/test/tester/own/own_global_script.ts") + tslang_add_test(NAME test-compile-${global_mm}-own-global-script COMMAND test-runner -mm=${global_mm} "${PROJECT_SOURCE_DIR}/test/tester/own/own_global_script.ts") +endforeach() + # Across a library boundary: the library exports its functions' `__own_no_drops`, and a borrow held # across a call to one survives. Run as a -shared pair (the library loaded at run time) and, on # Windows, checked against a library that lists nothing (own-shared-no-drops.cmake). diff --git a/tslang/test/tester/own/own_global_script.ts b/tslang/test/tester/own/own_global_script.ts new file mode 100644 index 000000000..8c86a392e --- /dev/null +++ b/tslang/test/tester/own/own_global_script.ts @@ -0,0 +1,45 @@ +// A global declared in top-level code owns what it is given: `const t = new C()` there stored the +// instance with no reference of its own, and the reference was then given back at the end of the +// block that made it, leaving the global pointing at a freed block. A `var` in a nested block is +// still a global, and the rest of the top-level code reads it after that block has ended. +class Holder { + v: number; + s: string; + + constructor(v: number) { + this.v = v; + this.s = "holder-" + v; + } +} + +function churn() { + let keep: Holder[] = []; + for (let i = 0; i < 1000; i++) keep.push(new Holder(i)); + return keep.length; +} + +function readAll() { + return held.v + later.v + text.length; +} + +const held = new Holder(7); +let later = new Holder(8); +var text = "x" + held.v; + +{ + var inBlock = new Holder(9); +} + +assert(churn() == 1000); +assert(held.v == 7 && held.s == "holder-7"); +assert(later.v == 8 && later.s == "holder-8"); +assert(text == "x7"); +assert(inBlock.v == 9 && inBlock.s == "holder-9"); +assert(readAll() == 17); + +// overwritten: the old value is given up +later = new Holder(10); +assert(churn() == 1000); +assert(later.s == "holder-10"); + +print("done.");