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
81 changes: 81 additions & 0 deletions tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,81 @@ class OwnedReturnConsumptionPass
return it != assigns.end() && it->getValue().contains(global);
};

// What a hold keeps of an argument: the value read before the call. Under rc a view of it is
// made at the call, after every argument is evaluated - after a later one may have given
// the global a new value - so the value read is the one behind it: the object a cast or an
// interface is made over (`f(G, (G = mk(), x))` with an interface parameter), or the values
// an object literal was given (`f({ v: G.v }, ..)`), which the literal holds with no count
// until it is boxed for the view, at the call too.
auto readsOf = [&](mlir::Operation *call, mlir::Value argument, llvm::SmallVectorImpl<mlir::Value> &reads) {
if (!compileOptions.isRefCounted())
{
reads.push_back(argument);
return;
}

auto value = argument;
for (;;)
{
if (auto castOp = value.getDefiningOp<mlir_ts::CastOp>())
{
value = castOp.getIn();
}
else if (auto newInterfaceOp = value.getDefiningOp<mlir_ts::NewInterfaceOp>())
{
value = newInterfaceOp.getThisVal();
}
else
{
break;
}
}

// an object literal boxed for an interface: stored into a new value_ref
auto newOp = value.getDefiningOp<mlir_ts::NewOp>();
if (newOp && mlir::isa<mlir_ts::ValueRefType>(newOp.getInstance().getType()))
{
for (auto *user : newOp->getUsers())
{
auto storeOp = mlir::dyn_cast<mlir_ts::StoreOp>(user);
if (storeOp && storeOp.getReference() == newOp.getInstance())
{
value = storeOp.getValue();
break;
}
}
}

// an object literal: a read of the slot it was built in, in the call's block
auto loadOp = value.getDefiningOp<mlir_ts::LoadOp>();
auto varOp = loadOp ? loadOp.getReference().getDefiningOp<mlir_ts::VariableOp>() : mlir_ts::VariableOp();
if (varOp && !varOp->hasAttr(OWNED_LOCAL_ATTR_NAME) && varOp->getBlock() == call->getBlock() &&
mlir::isa<mlir_ts::TupleType>(value.getType()))
{
for (auto *user : varOp->getUsers())
{
auto fieldRef = mlir::dyn_cast<mlir_ts::PropertyRefOp>(user);
if (!fieldRef)
{
continue;
}

for (auto *fieldUser : fieldRef->getUsers())
{
auto storeOp = mlir::dyn_cast<mlir_ts::StoreOp>(fieldUser);
if (storeOp && storeOp.getReference() == fieldRef.getResult())
{
reads.push_back(storeOp.getValue());
}
}
}

return;
}

reads.push_back(value);
};

llvm::SmallVector<std::pair<mlir::Operation *, mlir::Value>> held;
module.walk([&](mlir::Operation *op) {
if (!own_facts::isCall(op) || op->hasTrait<mlir::OpTrait::IsTerminator>() ||
Expand All @@ -457,6 +532,7 @@ class OwnedReturnConsumptionPass
}

llvm::SmallPtrSet<mlir::Value, 4> seen;
llvm::SmallVector<mlir::Value> reads;
for (auto argument : own_facts::callArgs(op))
{
// a method's object: `this` of the method bound to it
Expand All @@ -469,6 +545,11 @@ class OwnedReturnConsumptionPass
}
}

readsOf(op, argument, reads);
}

for (auto argument : reads)
{
if (!mth.ownsHeapMemory(op->getLoc(), argument.getType()) || !seen.insert(argument).second)
{
continue;
Expand Down
3 changes: 3 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ tslang_add_test(NAME test-compile-00-const-field-read COMMAND test-runner "${PRO
tslang_add_test(NAME test-compile-00-const-element-read COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_element_read.ts")
tslang_add_test(NAME test-compile-00-const-global-read COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_global_read.ts")
tslang_add_test(NAME test-compile-00-global-argument-reassigned COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_argument_reassigned.ts")
tslang_add_test(NAME test-compile-00-global-argument-view COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_argument_view.ts")
tslang_add_test(NAME test-compile-00-array-data-string-alias COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_data_string_alias.ts")
tslang_add_test(NAME test-compile-00-arrays7 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array7.ts")
tslang_add_test(NAME test-compile-00-arrays8-tuple-spread COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array8_tuple_spread.ts")
Expand Down Expand Up @@ -982,6 +983,7 @@ tslang_add_test(NAME test-jit-00-const-field-read COMMAND test-runner -jit "${PR
tslang_add_test(NAME test-jit-00-const-element-read COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_element_read.ts")
tslang_add_test(NAME test-jit-00-const-global-read COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_global_read.ts")
tslang_add_test(NAME test-jit-00-global-argument-reassigned COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_argument_reassigned.ts")
tslang_add_test(NAME test-jit-00-global-argument-view COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_argument_view.ts")
tslang_add_test(NAME test-jit-00-array-data-string-alias COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_data_string_alias.ts")
tslang_add_test(NAME test-jit-00-arrays7 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array7.ts")
tslang_add_test(NAME test-jit-00-arrays8-tuple-spread COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array8_tuple_spread.ts")
Expand Down Expand Up @@ -1950,6 +1952,7 @@ set(TSLANG_CORPUS
00const_element_read.ts
00const_global_read.ts
00global_argument_reassigned.ts
00global_argument_view.ts
00array_data_string_alias.ts
00array7.ts
00array8_tuple_spread.ts
Expand Down
109 changes: 109 additions & 0 deletions tslang/test/tester/tests/00global_argument_view.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// An argument read out of a global and given to a call as a view - an interface, a base class, an
// object literal holding it - stays alive when a later argument gives the global a new value:
// `check(G, (G = mk(2), 0))` with an interface parameter, `check({ c: G.c }, (G = mk(2), 0))`.
// The view is made at the call, after every argument is evaluated, so under rc a hold taken on the
// view (00global_argument_reassigned.ts) came after the later argument had freed the block, and an
// object literal held what it was given with no count until then. Each read follows allocations of
// the same size, so a freed block is reused.
class Base {
tag = "";
constructor(public x: number) {
this.tag = "c" + x;
}
}

class C extends Base {
v: number[] = [];
}

class H {
c: C = mk(3);
}

interface IC {
x: number;
tag: string;
}

interface IHolder {
c: C;
}

function mk(x: number): C {
const r = new C(x);
r.v.push(x);
return r;
}

function churn() {
const junk: C[] = [];
for (let i = 0; i < 64; i++) {
const c = new C(999);
c.tag = "junk";
c.v.push(99);
junk.push(c);
}
}

let G: C = mk(1);
const GH = new H();

function thrower(): number {
throw "thrown";
}

function viaInterface(c: IC, n: number, x: number) {
churn();
return c.x == x && c.tag == "c" + x;
}

function viaBase(c: Base, n: number, x: number) {
churn();
return c.x == x && c.tag == "c" + x;
}

function viaHolder(h: IHolder, n: number, x: number) {
churn();
return h.c.x == x && h.c.tag == "c" + x && h.c.v[0] == x;
}

function viaRecord(h: { c: C }, n: number, x: number) {
churn();
return h.c.x == x && h.c.tag == "c" + x && h.c.v[0] == x;
}

function inLoop() {
for (let i = 0; i < 3; i++) {
const x = G.x;
assert(viaInterface(G, (G = mk(10 + i), 0), x), "an interface over a global, in a loop");
const y = G.x;
assert(viaHolder({ c: G }, (G = mk(20 + i), 0), y), "an object literal holding a global, in a loop");
}
}

function inTryCatch() {
G = mk(30);
let caught = false;
try {
viaInterface(G, (G = mk(31), thrower()), 30);
} catch (e) {
caught = true;
}

churn();
assert(caught && G.x == 31 && G.tag == "c31", "a later argument throws after the assignment");
}

function main() {
assert(viaInterface(G, (G = mk(2), 0), 1), "an interface over a global");
assert(viaBase(G, (G = mk(3), 0), 2), "a global as its base class");
assert(viaInterface(GH.c, (GH.c = mk(4), 0), 3), "an interface over a global's field");
assert(viaHolder({ c: G }, (G = mk(5), 0), 3), "an object literal holding a global");
assert(viaHolder({ c: GH.c }, (GH.c = mk(6), 0), 4), "an object literal holding a global's field");
assert(viaRecord({ c: G }, (G = mk(7), 0), 5), "an object literal given as a record");
inLoop();
inTryCatch();
churn();
assert(G.x == 31 && GH.c.x == 6, "the globals' last values");
print("done.");
}
Loading