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
32 changes: 32 additions & 0 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1289,6 +1289,30 @@ class MLIRGenImpl
definingOp->setAttr(OWNED_RESULT_ATTR_NAME, builder.getUnitAttr());
}

// A store of a local's own value back into it: `value` is a plain read of `reference`, a local's slot, made
// in this block with only reads after it, so the slot still holds what was read. An atomic or volatile
// access is the program's to keep.
bool isSelfStore(mlir::Value value, mlir::Value reference)
{
auto loadOp = value.getDefiningOp<mlir_ts::LoadOp>();
if (!loadOp || loadOp.getReference() != reference || !reference.getDefiningOp<mlir_ts::VariableOp>() ||
loadOp->getBlock() != builder.getInsertionBlock() || loadOp->hasAttr(ATOMIC_ATTR_NAME) ||
loadOp->hasAttr(VOLATILE_ATTR_NAME))
{
return false;
}

for (auto *op = loadOp->getNextNode(); op; op = op->getNextNode())
{
if (!mlir::isa<mlir_ts::LoadOp>(op))
{
return false;
}
}

return true;
}

// Storage that hands ownership over when it is overwritten: the incoming value gains an
// owner and the outgoing one loses one.
//
Expand Down Expand Up @@ -6517,6 +6541,14 @@ class MLIRGenImpl
storeReference = builder.clone(*elementRefOp)->getResult(0);
}

// `n = n`, or `n = (n = f(), n)`: the value was just read out of this local and nothing since has
// written anywhere, so storing it back changes nothing. Emitting it anyway costs -mm=own the
// local: the read moves the value out, and the release of what the slot held came after it.
if (isSelfStore(savingValue, storeReference))
{
return mlir::success();
}

// Overwriting owning storage hands the count over: the incoming value gains an
// owner and the outgoing one loses it. Retaining first is what makes `x = x` safe -
// releasing first could drop the last reference and free the value about to be
Expand Down
7 changes: 7 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ tslang_add_test(NAME test-compile-00-generator-temporaries COMMAND test-runner "
tslang_add_test(NAME test-compile-00-conditional-named-const-release COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00conditional_named_const_release.ts")
tslang_add_test(NAME test-compile-00-array-length-valid COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts")
tslang_add_test(NAME test-compile-00-const-interface-of-local COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_interface_of_local.ts")
tslang_add_test(NAME test-compile-00-self-assign COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00self_assign.ts")
tslang_add_test(NAME test-compile-00-string-view-copy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_view_copy.ts")
tslang_add_test(NAME test-compile-00-global-copy-counted COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_copy_counted.ts")
tslang_add_test(NAME test-compile-00-global-interface-initializer COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_interface_initializer.ts")
Expand Down Expand Up @@ -997,6 +998,7 @@ tslang_add_test(NAME test-jit-00-generator-temporaries COMMAND test-runner -jit
tslang_add_test(NAME test-jit-00-conditional-named-const-release COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00conditional_named_const_release.ts")
tslang_add_test(NAME test-jit-00-array-length-valid COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts")
tslang_add_test(NAME test-jit-00-const-interface-of-local COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_interface_of_local.ts")
tslang_add_test(NAME test-jit-00-self-assign COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00self_assign.ts")
tslang_add_test(NAME test-jit-00-string-view-copy COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_view_copy.ts")
tslang_add_test(NAME test-jit-00-global-copy-counted COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_copy_counted.ts")
tslang_add_test(NAME test-jit-00-global-interface-initializer COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_interface_initializer.ts")
Expand Down Expand Up @@ -1986,6 +1988,7 @@ set(TSLANG_CORPUS
00conditional_named_const_release.ts
00array_length_valid.ts
00const_interface_of_local.ts
00self_assign.ts
00string_view_copy.ts
00global_copy_counted.ts
00global_interface_initializer.ts
Expand Down Expand Up @@ -3853,6 +3856,10 @@ tslang_add_test(NAME test-compile-own-00-generator-temporaries COMMAND test-runn
tslang_add_test(NAME test-jit-own-00-const-interface-of-local COMMAND test-runner -jit -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_interface_of_local.ts")
tslang_add_test(NAME test-compile-own-00-const-interface-of-local COMMAND test-runner -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_interface_of_local.ts")

# A local given its own value keeps it under own: `n = n` stores nothing.
tslang_add_test(NAME test-jit-own-00-self-assign COMMAND test-runner -jit -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00self_assign.ts")
tslang_add_test(NAME test-compile-own-00-self-assign COMMAND test-runner -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00self_assign.ts")

# Shared<T> (spec 23): counted handles. These are not in the own_test list, whose
# test-own-no-counting check forbids __tslang_inc_ref: a handle is counted under own by design.
# own_shared_user_named: a program's own class named Shared wins over the built-in (spec 23.1).
Expand Down
50 changes: 50 additions & 0 deletions tslang/test/tester/tests/00self_assign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// A local given its own value - `n = n`, or `n = (n = mk(5), n)` - keeps it, and can be moved or read
// afterwards. Under own the store back read the value out of the local first, which counted as moving
// it, so the release of what the local held came after the move and the program was refused. Each
// read follows allocations of the same size, so a freed block would be reused.
class V {
tag = "";
constructor(public x: number) {
this.tag = "v" + x;
}
}

class Junk {
a = -1;
b = -2;
tag = "junk";
}

function churn() {
let keep: Junk[] = [];
for (let i = 0; i < 1000; i++) keep.push(new Junk());
return keep.length;
}

function mk(x: number) {
return new V(x);
}

function take(v: V) {
churn();
return v.tag == "v" + v.x ? v.x : -1;
}

function main() {
let s = 0;
for (let i = 0; i < 3; i++) {
let n = mk(i);
n = n;
const m = n;
churn();
s += take(m);
}
assert(s == 3, "n = n, then moved");

let k = mk(1);
k = (k = mk(5), k);
churn();
assert(take(k) == 5, "n = (n = mk(5), n)");

print("done.");
}
Loading