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
29 changes: 28 additions & 1 deletion tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1656,8 +1656,35 @@ class OwnedReturnConsumptionPass
(mlir::isa_and_nonnull<mlir_ts::VariableOp>(slot) && slot->hasAttr(OWNED_LOCAL_ATTR_NAME)));
};

// ...or some branch yields a record literal it built whose field holds a temporary it made
// (`c ? { start: this.pos, dir: new V(1) } : ...`): the branch gives the temporary back before
// it ends, so the receiver's retain after the `ts.If` - which retains the fields - comes too
// late, and the field is freed under it. The branch retains it first. Not under -mm=own,
// which settles a literal's fields itself.
auto yieldsLiteralOfBranchTemporary = [&](mlir_ts::ResultOp resultOp) {
auto loadOp = producerOf(resultOp->getOperand(0)).getDefiningOp<mlir_ts::LoadOp>();
auto varOp = loadOp ? loadOp.getReference().getDefiningOp<mlir_ts::VariableOp>() : mlir_ts::VariableOp();
if (compileOptions.memoryModel == MemoryModelOwn || !varOp ||
varOp->getParentRegion() != resultOp->getParentRegion())
{
return false;
}

return llvm::any_of(varOp->getUsers(), [&](mlir::Operation *user) {
auto propertyRefOp = mlir::dyn_cast<mlir_ts::PropertyRefOp>(user);
return propertyRefOp && llvm::any_of(propertyRefOp->getUsers(), [&](mlir::Operation *fieldUser) {
auto storeOp = mlir::dyn_cast<mlir_ts::StoreOp>(fieldUser);
auto stored = storeOp && storeOp.getReference() == propertyRefOp.getResult()
? producerOf(storeOp.getValue())
: mlir::Value();
return stored && stored.getDefiningOp() && ::typescript::mayTakeOverReference(stored) &&
stored.getDefiningOp()->getParentRegion() == resultOp->getParentRegion();
});
});
};

if (llvm::none_of(yields, carriesOwned) && llvm::none_of(yields, borrowsBranchTemporary) &&
llvm::none_of(yields, readsNamedObjectField))
llvm::none_of(yields, readsNamedObjectField) && llvm::none_of(yields, yieldsLiteralOfBranchTemporary))
{
return;
}
Expand Down
5 changes: 5 additions & 0 deletions tslang/lib/TypeScript/OwnershipFacts.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ constexpr int64_t OWN_INTERFACE_FIELD_POSITION = -3;
// The position of a captured variable's cell holding a string, assigned: the old string goes, and a
// string owns nothing else, so only a borrow that is a string may be it (spec 52)
constexpr int64_t OWN_CAPTURED_STRING_POSITION = -4;
// The position of a captured variable's cell holding a block (an object, an array), assigned: the old
// value goes with all it owns, and only the cell owns it, so only a borrow read through a captured
// variable's cell may be under it. A call given anything read through a variable a closure assigns
// is an error where it is made (checkArgumentsAssigned), so no parameter of the callee is under it.
constexpr int64_t OWN_CAPTURED_CELL_POSITION = -5;
// On a function whose body relies on a fact its callers cannot all know (a parameter it keeps, a
// result that borrows): why, for the error the body gets instead.
#define OWN_FACTS_LOST_ATTR_NAME "__own_facts_lost"
Expand Down
28 changes: 27 additions & 1 deletion tslang/lib/TypeScript/OwnershipInferenceBorrowers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ namespace own_inference
}
}

// what a view of it shows (`f(n, ...)` with `n` given as an interface): the value read
for (auto depth = 0; depth < 8 && isView(argument.getDefiningOp()) && !argument.getDefiningOp<mlir_ts::CastOp>();
depth++)
{
argument = argument.getDefiningOp()->getOperand(0);
}

auto *read = argument.getDefiningOp();
auto local = mlir::dyn_cast_or_null<mlir_ts::VariableOp>(slotOf(argument));
if (!read || !local || !ownsHeap(argument) || isHandle(argument))
Expand All @@ -48,12 +55,31 @@ namespace own_inference
return (storeOp && storeOp.getReference() == local.getResult()) ||
(releaseSlotOp && releaseSlotOp.getSlot() == local.getResult());
});
// ...or the call itself, when it may run a closure that gives a captured object a new value
// (OWN_CAPTURED_CELL_POSITION): its callee's borrow of the argument would not see that
auto places = callDropFields(call);
if (!write && assignedByClosure && places && llvm::any_of(*places, [](auto &place) {
return place.first == OWN_CAPTURED_CELL_POSITION;
}))
{
write = call;
}

if (!write)
{
continue;
}

if (reporting())
if (reporting() && write == call)
{
auto name = varName(local);
auto callee = calleeName(call);
call->emitError("'") << name << "' is lent to '" << (callee.empty() ? "a function" : callee)
<< "', which may run a closure that gives '" << name
<< "' a new value; -mm=own cannot prove it outlives the call";
signalPassFailure();
}
else if (reporting())
{
auto name = varName(local);
auto callee = calleeName(call);
Expand Down
12 changes: 12 additions & 0 deletions tslang/lib/TypeScript/OwnershipInferenceClosures.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,18 @@ namespace own_inference
return;
}

// a callee that may overwrite an element (`p[0] = v`) gives back the old one, which is not
// the array's to give: the borrowed value is freed under its owner, and the new one is
// lost with the array's shallow release. So is a callee that may destroy anything
auto elementSlot = mlir_ts::RefType::get(mlir::cast<mlir_ts::ArrayType>(result.getType()).getElementType());
auto places = callDropFields(call);
if (!callNoDrops(call) && (!places || llvm::any_of(*places, [&](auto &place) {
return place.first == -1 && place.second == elementSlot;
})))
{
return;
}

// rc's references for the elements go: the array borrows them, for as long as the call
setAttrTracked(arrayOp, OWN_BORROWING_ARRAY_ATTR_NAME);
fieldBorrowRetains.insert(retains.begin(), retains.end());
Expand Down
137 changes: 122 additions & 15 deletions tslang/lib/TypeScript/OwnershipInferencePass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ namespace own_inference
}

// Does nothing `value` gives - its own value, a field read out of it, a call's result that may be it - leave
// `block`? Then the last of them that uses it is collected into `last`. A scalar is not followed: it is a copy.
// `block` (the function, when null)? Then the last of them that uses it is collected into `last` (in `block`
// only). A scalar is not followed: it is a copy.
static bool staysInBlock(mlir::Value value, mlir::Block *block, mlir::Operation *&last, int depth = 0)
{
auto isScalar = [](mlir::Type type) {
Expand All @@ -75,12 +76,12 @@ namespace own_inference

for (auto *user : value.getUsers())
{
if (user->getBlock() != block)
if (block && user->getBlock() != block)
{
return false;
}

if (!last || last->isBeforeInBlock(user))
if (block && (!last || last->isBeforeInBlock(user)))
{
last = user;
}
Expand All @@ -95,12 +96,12 @@ namespace own_inference
{
for (auto *refUser : user->getResult(0).getUsers())
{
if (refUser->getBlock() != block)
if (block && refUser->getBlock() != block)
{
return false;
}

if (last->isBeforeInBlock(refUser))
if (block && last->isBeforeInBlock(refUser))
{
last = refUser;
}
Expand All @@ -115,10 +116,17 @@ namespace own_inference
continue;
}

// an argument: the result may be it, or borrowed from it
auto callIndirectOp = mlir::dyn_cast<mlir_ts::CallIndirectOp>(user);
if ((mlir::isa<mlir_ts::SymbolCallInternalOp>(user) || callIndirectOp) &&
(!callIndirectOp || callIndirectOp.getCallee() != value))
// a method of it (`i.item.get()`): the method bound to it, its object and its function, which only
// the call below uses
if (mlir::isa<mlir_ts::ThisSymbolRefOp, mlir_ts::ThisVirtualSymbolRefOp, mlir_ts::GetThisOp,
mlir_ts::GetMethodOp>(user) &&
user->getNumResults() == 1 && follow(user->getResult(0)))
{
continue;
}

// an argument, or the method called: the result may be it, or borrowed from it
if (mlir::isa<mlir_ts::SymbolCallInternalOp, mlir_ts::CallIndirectOp>(user))
{
for (auto result : user->getResults())
{
Expand Down Expand Up @@ -161,18 +169,117 @@ namespace own_inference
continue;
}

mlir::OpBuilder builder(loadOp->getContext());
auto release = [&](mlir::Operation *at) {
auto releaseOp = builder.create<mlir_ts::ReleaseOp>(loadOp.getLoc(), loadOp.getResult());
releaseOp->setAttr(OWN_CELL_READ_ATTR_NAME, builder.getUnitAttr());
};

mlir::Operation *last = nullptr;
if (!staysInBlock(loadOp.getResult(), loadOp->getBlock(), last) || !last ||
last->hasTrait<mlir::OpTrait::IsTerminator>())
if (staysInBlock(loadOp.getResult(), loadOp->getBlock(), last) && last &&
!last->hasTrait<mlir::OpTrait::IsTerminator>())
{
builder.setInsertionPointAfter(last);
release(last);
continue;
}

mlir::OpBuilder builder(last->getContext());
builder.setInsertionPointAfter(last);
auto releaseOp = builder.create<mlir_ts::ReleaseOp>(loadOp.getLoc(), loadOp.getResult());
releaseOp->setAttr(OWN_CELL_READ_ATTR_NAME, builder.getUnitAttr());
// ...or put in a `let` (`let x = i.item`) that is only read, across blocks too, and nothing read
// out of it leaves the function: freed at the function's returns, when the read reaches them all
// and the object it reads through is a parameter's, which outlives them
auto exits = localHeldToExits(loadOp);
for (auto *exit : exits)
{
builder.setInsertionPoint(exit);
release(exit);
}

// ...else a getter's new value would be lost: returned, stored, or kept where nothing frees it. Only
// a field some class of this module fills with accessors has one; its value cannot leave its read
auto refOp = loadOp.getReference().getDefiningOp<mlir_ts::InterfaceSymbolRefOp>();
if (!exits.empty() || !refOp)
{
continue;
}

auto fieldAttr = mlir::dyn_cast<mlir::StringAttr>(refOp.getIdentifier());
auto filler = fieldAttr ? accessorFiller(fieldAttr.getValue()) : llvm::StringRef();
if (!filler.empty())
{
loadOp.emitError("'")
<< fieldAttr.getValue() << "' is read through an interface, and '" << filler
<< "' fills it with a getter, whose new value -mm=own frees only where it is read: it cannot "
"be returned, kept or outlive its block; read the field where it is used";
signalPassFailure();
}
}
}

llvm::StringRef OwnershipInferencePass::accessorFiller(llvm::StringRef field)
{
// the cell of a class's accessors for an interface's field: `<class>.<interface>..vtbl.<field>.accessor`
auto suffix = (llvm::Twine(".") + field + ".accessor").str();
auto module = getFunction()->getParentOfType<mlir::ModuleOp>();
llvm::StringRef found;
module.walk([&](mlir_ts::GlobalOp globalOp) {
auto name = globalOp.getSymName();
auto vtbl = name.find("..vtbl.");
if (vtbl != llvm::StringRef::npos && name.ends_with(suffix) && vtbl + 6 + suffix.size() == name.size())
{
// the class: up to the interface's name
auto head = name.take_front(vtbl);
found = head.take_front(head.rfind('.') == llvm::StringRef::npos ? head.size() : head.rfind('.'));
return mlir::WalkResult::interrupt();
}

return mlir::WalkResult::advance();
});

return found;
}

llvm::SmallVector<mlir::Operation *> OwnershipInferencePass::localHeldToExits(mlir_ts::LoadOp read)
{
auto result = read.getResult();
auto varOp = result.hasOneUse() ? mlir::dyn_cast<mlir_ts::VariableOp>(*result.getUsers().begin()) : mlir_ts::VariableOp();
if (!varOp || varOp.getInitializer() != result || varOp.getCaptured().value_or(false))
{
return {};
}

// only read, and what is read stays in the function
for (auto *user : varOp->getUsers())
{
mlir::Operation *ignored = nullptr;
auto slotRead = mlir::dyn_cast<mlir_ts::LoadOp>(user);
if (!mlir::isa<mlir_ts::DebugVariableOp>(user) &&
(!slotRead || !staysInBlock(slotRead.getResult(), /*block=*/nullptr, ignored)))
{
return {};
}
}

// read through a parameter: `i.item` of an interface parameter `i`
auto refOp = read.getReference().getDefiningOp<mlir_ts::InterfaceSymbolRefOp>();
auto objectRead = refOp ? refOp->getOperand(0).getDefiningOp<mlir_ts::LoadOp>() : mlir_ts::LoadOp();
auto objectSlot = objectRead ? objectRead.getReference().getDefiningOp<mlir_ts::VariableOp>() : mlir_ts::VariableOp();
auto index = -1;
if (!objectSlot || !isParameterSlot(objectSlot, index))
{
return {};
}

// every return, after the read
auto f = getFunction();
mlir::DominanceInfo dominanceInfo(f);
llvm::SmallVector<mlir::Operation *> exits;
auto all = true;
f.walk([&](mlir_ts::ReturnInternalOp returnOp) {
exits.push_back(returnOp);
all = all && dominanceInfo.properlyDominates(read.getOperation(), returnOp.getOperation());
});

return all ? exits : llvm::SmallVector<mlir::Operation *>();
}

bool OwnershipInferencePass::analyze(llvm::SetVector<mlir::Operation *> &toErase)
Expand Down
5 changes: 5 additions & 0 deletions tslang/lib/TypeScript/OwnershipInferencePass.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ class OwnershipInferencePass
// what is borrowed from it stay in its block: a release after their last use, for the getter's path only. One
// nothing uses - what an assignment `i.f = v` leaves of the place it wrote - goes.
void releaseCellReads();
// The returns of the function, where a read through a parameter's interface field put in a `let` that is
// only read is released (releaseCellReads); none when the read does not reach them all or may leave.
llvm::SmallVector<mlir::Operation *> localHeldToExits(mlir_ts::LoadOp read);
// The class of this module that fills an interface's field of this name with accessors, if any.
llvm::StringRef accessorFiller(llvm::StringRef field);

// The analysis of one function; true when it passed. In a dry run it erases nothing and strips
// no facts.
Expand Down
12 changes: 12 additions & 0 deletions tslang/lib/TypeScript/OwnershipInferencePlaceReads.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,18 @@ namespace own_inference
return !notString;
}

// a captured object assigned frees what only its cell owns: a chain read through
// a captured variable's cell may be under it, nothing else (OWN_CAPTURED_CELL_POSITION)
if (place.first == OWN_CAPTURED_CELL_POSITION)
{
return llvm::any_of(chain.roots, [](auto &root) {
auto loadOp = root.first.template getDefiningOp<mlir_ts::LoadOp>();
auto through = loadOp ? loadOp.getReference() : mlir::Value();
return isCellVariable(root.first) || isLoadedCell(root.first) ||
(through && (isCellVariable(through) || isLoadedCell(through)));
});
}

return overwritesChain(place.first, place.second, chain);
});
}
Expand Down
18 changes: 17 additions & 1 deletion tslang/lib/TypeScript/OwnershipInferenceRecords.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,8 @@ namespace own_inference
continue;
}

if (auto branchOp = mlir::dyn_cast<mlir::BranchOpInterface>(user))
// (not a call in a `try`, a `ts.Invoke`: a branch too, but its operands are the call's)
if (auto branchOp = mlir::dyn_cast<mlir::BranchOpInterface>(user); branchOp && !isCall(user))
{
if (auto argument = branchOp.getSuccessorBlockArgument(use.getOperandNumber()))
{
Expand Down Expand Up @@ -519,6 +520,21 @@ namespace own_inference
continue;
}

// a callee that may overwrite a field of what it is given (`p.v = ...`) gives back
// the old value, which may be one the record borrows - as a write of the field here
// would
if (pending.record && !isCalleeOperand(use) && !callNoDrops(user))
{
auto places = callDropFields(user);
if (places && llvm::any_of(*places, [](auto &place) {
return place.first >= 0 || place.first == OWN_INTERFACE_FIELD_POSITION;
}))
{
keep(user);
continue;
}
}

// a result that borrows it - a plain borrow (`id(r)`), or a borrowing record that
// holds it - points where it points
auto args = callArgs(user);
Expand Down
6 changes: 6 additions & 0 deletions tslang/lib/TypeScript/OwnershipSignaturePass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1508,6 +1508,12 @@ namespace own_signature
// a captured string assigned (`each += v` in a callback): it frees that string only
drops.add({OWN_CAPTURED_STRING_POSITION, slot.getType()});
}
else if (isLoadedCell(slot) && !touchesHandle(op))
{
// a captured object assigned (`last = new Box()` in a callback): it frees what the cell
// owns, which only a read through a captured variable may borrow
drops.add({OWN_CAPTURED_CELL_POSITION, slot.getType()});
}
else if (slot.getDefiningOp<mlir_ts::AddressOfOp>() || isLoadedCell(slot) ||
(!isPlace(slot) && touchesHandle(op) && !isCountedReadSlot(slot)))
{
Expand Down
Loading
Loading