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
6 changes: 4 additions & 2 deletions docs/memory-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,10 @@ includes an object that is:
- returned, thrown, stored in a field, array, global or captured variable, or converted to
`any`, an interface or a union;
- passed to a function in another module, including the default library;
- made in a function that has a `try`, or is `async`, a generator, or recursive;
- still in use when the next iteration of its loop makes a new one.
- made in a function that is `async`, a generator, or recursive, or - under `-mm=rc` and
`-mm=own` - one that has a `try`;
- still in use when the next iteration of its loop makes a new one. A use on the way out of the
loop - a `return` or a `break` out of a search - is not.

Under `-mm=rc` and `-mm=own`, an object that owns memory stays on the heap in a module that uses
`Shared<T>`, because `Shared.count` would see its contents given back later.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ Strings are not in the first version (they are produced by runtime/DefaultLib ca
All promoted allocas go in the entry block (already what `Alloca` does). A site inside a loop
reuses one slot per iteration, which is correct only if the object does not live into the next
iteration: a value from iteration *n* still reachable in iteration *n+1* (stored in a local
declared outside the loop, for instance) is an escape for this purpose.
declared outside the loop, for instance) is an escape for this purpose. A use outside the loop
from which the site cannot run again - a `return` or a `break` out of a search - sees the last
iteration's object and is not.

## 6. Per memory model

Expand Down
17 changes: 13 additions & 4 deletions tslang/include/TypeScript/LowerToLLVM/OwnershipRoutineLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,13 @@ class OwnershipRoutineLogic
}

// What destroying a block of `storageType` at `payloadPtr` releases, without freeing the block:
// a promoted object's fields when its frame is done with it (ts.ReleaseContents).
// a promoted object's fields when its frame is done with it (ts.ReleaseContents). Each field
// given back is nulled, so that the block never holds a reference it no longer owns: a second
// ts.ReleaseContents, or a release of a field, finds nothing to give back. At a return the
// stores are dead, and LLVM drops them.
void emitReleaseContents(mlir::Type storageType, mlir::Value payloadPtr)
{
releaseFields(storageType, payloadPtr);
releaseFields(storageType, payloadPtr, /*nullReleased=*/true);
}

// Drops one reference held by the value in `slotPtr`, whose TypeScript type is `type`.
Expand Down Expand Up @@ -730,8 +733,9 @@ class OwnershipRoutineLogic
FlatSymbolRefAttr::get(rewriter.getContext(), routineName), ValueRange{slotPtr});
}

// Releases each field a record-shaped value owns. `basePtr` addresses the record itself.
void releaseFields(mlir::Type recordType, mlir::Value basePtr)
// Releases each field a record-shaped value owns. `basePtr` addresses the record itself. With
// `nullReleased`, each field released is then zeroed.
void releaseFields(mlir::Type recordType, mlir::Value basePtr, bool nullReleased = false)
{
TypeHelper th(rewriter);
TypeConverterHelper tch(typeConverter);
Expand Down Expand Up @@ -784,6 +788,11 @@ class OwnershipRoutineLogic
rewriter.create<LLVM::CallOp>(loc, TypeRange{},
FlatSymbolRefAttr::get(rewriter.getContext(), routineName),
ValueRange{fieldPtr});
if (nullReleased)
{
auto llvmFieldType = tch.convertType(fieldType);
rewriter.create<LLVM::StoreOp>(loc, rewriter.create<LLVM::ZeroOp>(loc, llvmFieldType), fieldPtr);
}
}
}

Expand Down
166 changes: 124 additions & 42 deletions tslang/lib/TypeScript/StackEscapeFacts.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,47 +104,149 @@ class StackEscapeFacts
// method they may reach.
bool isRecursive(mlir_ts::FuncOp funcOp)
{
auto it = recursive.find(funcOp);
if (it != recursive.end())
if (!recursive)
{
return it->second;
collectRecursive();
}

llvm::SmallPtrSet<mlir::Operation *, 16> seen;
llvm::SmallVector<mlir_ts::FuncOp> work{funcOp};
auto found = false;
while (!work.empty() && !found)
return recursive->contains(funcOp);
}

// Whether argument `argIndex` of a call through `candidates` may outlive the call: empty when
// every candidate's parameter stays inside it.
llvm::StringRef argumentEscapes(llvm::ArrayRef<mlir::FlatSymbolRefAttr> candidates, unsigned argIndex)
{
for (auto candidate : candidates)
{
auto current = work.pop_back_val();
for (auto callee : calleesOfFunction(current))
auto funcOp = module.lookupSymbol<mlir_ts::FuncOp>(candidate);
if (!funcOp || funcOp.getBody().empty() || argIndex >= funcOp.getBody().getNumArguments())
{
return "unknown-callee";
}

if (paramEscapes.lookup({funcOp, argIndex}))
{
return "argument";
}
}

return "";
}

private:
mlir::ModuleOp module;
std::optional<llvm::SmallPtrSet<mlir::Operation *, 16>> recursive;

// Every function on a cycle of the call graph, in one pass (Tarjan's strongly connected
// components, iterative: a module's call chains may be deeper than the native stack): a
// function is recursive when its component holds another function, or it calls itself.
void collectRecursive()
{
recursive.emplace();

llvm::SmallVector<mlir_ts::FuncOp> funcs;
llvm::DenseMap<mlir::Operation *, unsigned> numberOf;
module.walk([&](mlir_ts::FuncOp funcOp) {
numberOf[funcOp] = funcs.size();
funcs.push_back(funcOp);
});

// the edges, each callee once; a callee with no definition here is on no cycle of ours
llvm::SmallVector<llvm::SmallVector<unsigned>> edges(funcs.size());
llvm::SmallVector<bool> callsItself(funcs.size(), false);
for (auto [index, funcOp] : llvm::enumerate(funcs))
{
llvm::SmallPtrSet<mlir::Operation *, 16> seen;
for (auto callee : calleesOfFunction(funcOp))
{
auto calleeOp = module.lookupSymbol<mlir_ts::FuncOp>(callee);
if (!calleeOp)
if (!calleeOp || !seen.insert(calleeOp).second)
{
continue;
}

if (calleeOp == funcOp)
auto calleeIndex = numberOf.lookup(calleeOp);
callsItself[index] = callsItself[index] || calleeIndex == index;
edges[index].push_back(calleeIndex);
}
}

constexpr unsigned unvisited = ~0u;
llvm::SmallVector<unsigned> order(funcs.size(), unvisited);
llvm::SmallVector<unsigned> lowLink(funcs.size(), 0);
llvm::SmallVector<bool> onStack(funcs.size(), false);
llvm::SmallVector<unsigned> stack;
// the depth-first path: a function and the index of the next of its edges to follow
llvm::SmallVector<std::pair<unsigned, unsigned>> path;
unsigned nextOrder = 0;

for (auto root : llvm::seq<unsigned>(0, funcs.size()))
{
if (order[root] != unvisited)
{
continue;
}

auto visit = [&](unsigned node) {
order[node] = lowLink[node] = nextOrder++;
stack.push_back(node);
onStack[node] = true;
path.push_back({node, 0});
};

visit(root);
while (!path.empty())
{
auto &[node, edgeIndex] = path.back();
if (edgeIndex < edges[node].size())
{
found = true;
break;
auto callee = edges[node][edgeIndex++];
if (order[callee] == unvisited)
{
visit(callee);
}
else if (onStack[callee])
{
lowLink[node] = std::min(lowLink[node], order[callee]);
}

continue;
}

// every edge followed: `node` closes a component when nothing below reached higher
auto done = node;
path.pop_back();
if (!path.empty())
{
auto parent = path.back().first;
lowLink[parent] = std::min(lowLink[parent], lowLink[done]);
}

if (lowLink[done] != order[done])
{
continue;
}

if (seen.insert(calleeOp).second)
llvm::SmallVector<unsigned> component;
unsigned member;
do
{
member = stack.pop_back_val();
onStack[member] = false;
component.push_back(member);
} while (member != done);

if (component.size() > 1 || callsItself[done])
{
work.push_back(calleeOp);
for (auto index : component)
{
recursive->insert(funcs[index]);
}
}
}
}

recursive[funcOp] = found;
return found;
}

private:
mlir::ModuleOp module;
llvm::DenseMap<mlir::Operation *, bool> recursive;

// What a function may call. A call through a value the module cannot resolve may reach any
// function whose address is taken somewhere in it - a closure, a function stored in a variable -
// which is how a recursion through `const g = f; g()` or a closure is still seen.
Expand Down Expand Up @@ -268,26 +370,6 @@ class StackEscapeFacts
return !candidates.empty();
}

// Whether argument `argIndex` of a call through `candidates` may outlive the call.
llvm::StringRef argumentEscapes(llvm::ArrayRef<mlir::FlatSymbolRefAttr> candidates, unsigned argIndex)
{
for (auto candidate : candidates)
{
auto funcOp = module.lookupSymbol<mlir_ts::FuncOp>(candidate);
if (!funcOp || funcOp.getBody().empty() || argIndex >= funcOp.getBody().getNumArguments())
{
return "unknown-callee";
}

if (paramEscapes.lookup({funcOp, argIndex}))
{
return "argument";
}
}

return "";
}

// The functions a call's callee value may be, when the module can tell.
bool calleesOf(mlir::Value callee, llvm::SmallVectorImpl<mlir::FlatSymbolRefAttr> &candidates)
{
Expand Down
Loading
Loading