diff --git a/tslang/lib/TypeScript/OwnershipFacts.h b/tslang/lib/TypeScript/OwnershipFacts.h index 992003b45..7b461da4c 100644 --- a/tslang/lib/TypeScript/OwnershipFacts.h +++ b/tslang/lib/TypeScript/OwnershipFacts.h @@ -831,6 +831,39 @@ inline bool argumentHoldsNoBlock(mlir::Value arg) return !mlir::isa(arg.getType()) && holdsNoBlock(arg); } +// The one value a merge can bring a block in by: every other way into the block passes a default +// (`ts.Default`, `ts.Null`, `ts.Undef`) or what holds no block. `let [x, y] = a` reads each element +// behind a test of the array's length whose other side is the default, so `x` is that element read. +// Null when no way in, or more than one, passes a block. +inline mlir::Value soleBlockMergedInto(mlir::Value value) +{ + auto argument = mlir::dyn_cast(value); + llvm::SmallVector passed; + if (!argument || !mergedInto(argument, passed)) + { + return {}; + } + + mlir::Value sole; + for (auto incoming : passed) + { + if (mlir::isa_and_nonnull(incoming.getDefiningOp()) || + holdsNoBlock(incoming)) + { + continue; + } + + if (sole && sole != incoming) + { + return {}; + } + + sole = incoming; + } + + return sole; +} + // A value -mm=own copies rather than give a second owner (spec 22.1): a string, a string literal, // a union of strings and null that needs no tag (one pointer), or an optional of one of those // (`{ptr, i1}`). diff --git a/tslang/lib/TypeScript/OwnershipInferencePlaceReads.cpp b/tslang/lib/TypeScript/OwnershipInferencePlaceReads.cpp index 7a004e6e2..6bf732325 100644 --- a/tslang/lib/TypeScript/OwnershipInferencePlaceReads.cpp +++ b/tslang/lib/TypeScript/OwnershipInferencePlaceReads.cpp @@ -11,6 +11,13 @@ namespace own_inference mlir::Operation *OwnershipInferencePass::placeReadOf(mlir::Value value) { auto root = rootOf(value); + // a read behind a test whose other side is the default (`let [x] = a`) is that read; the + // walk from it (checkPlaceRead) goes on through the branch to what takes the merge + if (auto sole = soleBlockMergedInto(root)) + { + root = sole; + } + auto *def = root.getDefiningOp(); // a handle read out of a place that rc gives an owner of its own is a counted copy, not a // borrow (spec 23.3); any other is a borrow of the place, as a field read is diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 4253ea47e..a05c5de54 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -3600,7 +3600,7 @@ foreach(own_test own_fresh_string own_fresh_array own_return_new own_try_local o own_field_chain_let own_read_after_move own_conditional_move own_interface_facts own_declared_scalar_no_drops own_borrowing_record own_borrowing_record_arg - own_reduce own_fresh_call_result own_ternary_outer_loop) + own_reduce own_fresh_call_result own_ternary_outer_loop own_destructure_elements) 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. @@ -3686,6 +3686,16 @@ foreach(ternary_loop_mm rc gc none) tslang_add_test(NAME test-compile-${ternary_loop_mm}-own_ternary_outer_loop COMMAND test-runner -mm=${ternary_loop_mm} "${PROJECT_SOURCE_DIR}/test/tester/own/own_ternary_outer_loop.ts") endforeach() +# `let [a, b] = pair` reads each element behind a length test; under own the local is a borrow of the +# element, as `let a = pair[0]` is. The same program under every other model, and the corpus file +# whose destructuring own refused. +foreach(destructure_mm rc gc none) + tslang_add_test(NAME test-jit-${destructure_mm}-own_destructure_elements COMMAND test-runner -jit -mm=${destructure_mm} "${PROJECT_SOURCE_DIR}/test/tester/own/own_destructure_elements.ts") + tslang_add_test(NAME test-compile-${destructure_mm}-own_destructure_elements COMMAND test-runner -mm=${destructure_mm} "${PROJECT_SOURCE_DIR}/test/tester/own/own_destructure_elements.ts") +endforeach() +tslang_add_test(NAME test-jit-own-00-owned-locals COMMAND test-runner -jit -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00owned_locals.ts") +tslang_add_test(NAME test-compile-own-00-owned-locals COMMAND test-runner -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00owned_locals.ts") + # A call through an interface closed in its module gets its implementations' facts (spec 31); the # same program under every other model. foreach(interface_facts_mm rc gc none) @@ -4201,7 +4211,9 @@ set(own_error_cases "own_err_borrowing_record_kept_param|:30:12: error: '[^']*' borrows '[^']*' but is used here after it may be released or overwritten" "own_err_borrowing_record_place_overwritten|:28:11: error: '[^']*' borrows a field but is used here after it may be released or overwritten" "own_err_borrowing_record_open|:17:31: error: '[^']*' borrows '[^']*' and cannot be stored, returned or captured" - "own_err_borrowing_record_borrowed_again|:29:11: error: '[^']*' borrows the result of 'A[.]hit' but is used here after it may be released or overwritten") + "own_err_borrowing_record_borrowed_again|:29:11: error: '[^']*' borrows the result of 'A[.]hit' but is used here after it may be released or overwritten" + "own_err_destructure_element_reassigned|:10:11: error: '[^']*' borrows an element but is used here after it may be released or overwritten" + "own_err_destructure_element_returned|:9:5: error: '[^']*' borrows an element and cannot be stored, returned or captured") foreach(own_error_case ${own_error_cases}) string(FIND "${own_error_case}" "|" own_error_sep) string(SUBSTRING "${own_error_case}" 0 ${own_error_sep} own_error_name) diff --git a/tslang/test/tester/own/own_destructure_elements.ts b/tslang/test/tester/own/own_destructure_elements.ts new file mode 100644 index 000000000..d260db708 --- /dev/null +++ b/tslang/test/tester/own/own_destructure_elements.ts @@ -0,0 +1,57 @@ +// `let [a, b] = pair` reads each element behind a test of the array's length whose other side is +// the default. Under own that merge was refused ("merged from several branches"), though the plain +// read `let a = pair[0]` is a borrow of the element: the destructured local is that borrow, and a +// string is copied, as a string read out of an element always is (spec 22). Each read follows +// allocations of the same size, so a freed block would be reused. +class N { + tag = ""; + constructor(public x: number) { + this.tag = "n" + x; + } +} + +function churn() { + const keep: N[] = []; + for (let i = 0; i < 1000; i++) keep.push(new N(777)); + return keep.length; +} + +function mk(i: number) { + return "elem" + i; +} + +function classes() { + let pair = [new N(1), new N(2)]; + let [a, b] = pair; + churn(); + assert(a.tag == "n1" && b.tag == "n2", "class elements"); + return a.x + b.x; +} + +function strings() { + let s = [mk(1), mk(2)]; + let [x, y] = s; + s = [mk(9)]; + churn(); + assert(x == "elem1" && y == "elem2", "strings outlive their array"); + return x.length + y.length; +} + +function inLoop() { + let pair = [new N(3), new N(4)]; + let sum = 0; + for (let i = 0; i < 3; i++) { + let [a, b] = pair; + churn(); + sum += a.x * b.x; + } + + return sum; +} + +function main() { + assert(classes() == 3, "classes"); + assert(strings() == 10, "strings"); + assert(inLoop() == 36, "in a loop"); + print("done."); +} diff --git a/tslang/test/tester/own/own_err_destructure_element_reassigned.ts b/tslang/test/tester/own/own_err_destructure_element_reassigned.ts new file mode 100644 index 000000000..423aedcc9 --- /dev/null +++ b/tslang/test/tester/own/own_err_destructure_element_reassigned.ts @@ -0,0 +1,11 @@ +// `let [a] = pair` borrows pair's first element: giving pair a new array frees the one `a` reads. +class N { + constructor(public x: number) {} +} + +function main() { + let pair = [new N(1), new N(2)]; + let [a] = pair; + pair = [new N(3)]; + print(a.x); +} diff --git a/tslang/test/tester/own/own_err_destructure_element_returned.ts b/tslang/test/tester/own/own_err_destructure_element_returned.ts new file mode 100644 index 000000000..5c951cf59 --- /dev/null +++ b/tslang/test/tester/own/own_err_destructure_element_returned.ts @@ -0,0 +1,14 @@ +// `let [a] = pair` borrows pair's first element, which the end of the function releases with pair. +class N { + constructor(public x: number) {} +} + +function first() { + let pair = [new N(1), new N(2)]; + let [a] = pair; + return a; +} + +function main() { + print(first().x); +}