Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions tslang/lib/TypeScript/OwnershipFacts.h
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,39 @@ inline bool argumentHoldsNoBlock(mlir::Value arg)
return !mlir::isa<mlir_ts::OpaqueType>(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<mlir::BlockArgument>(value);
llvm::SmallVector<mlir::Value> passed;
if (!argument || !mergedInto(argument, passed))
{
return {};
}

mlir::Value sole;
for (auto incoming : passed)
{
if (mlir::isa_and_nonnull<mlir_ts::DefaultOp, mlir_ts::NullOp, mlir_ts::UndefOp>(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}`).
Expand Down
7 changes: 7 additions & 0 deletions tslang/lib/TypeScript/OwnershipInferencePlaceReads.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 14 additions & 2 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
57 changes: 57 additions & 0 deletions tslang/test/tester/own/own_destructure_elements.ts
Original file line number Diff line number Diff line change
@@ -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.");
}
11 changes: 11 additions & 0 deletions tslang/test/tester/own/own_err_destructure_element_reassigned.ts
Original file line number Diff line number Diff line change
@@ -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);
}
14 changes: 14 additions & 0 deletions tslang/test/tester/own/own_err_destructure_element_returned.ts
Original file line number Diff line number Diff line change
@@ -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);
}
Loading