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
7 changes: 7 additions & 0 deletions tslang/include/TypeScript/Defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@
// rest.
#define UNWIND_ONLY_ATTR_NAME "__unwind_only"

// The `return` counterpart: a release MLIRGen put at the end of a hand-written `finally` for what
// the scopes around the `try` owe once a `return` from its body or catch clause has run the
// finally. The `return` itself stops short of those scopes, since the finally may still read them
// (GenContext::finallyGuardScope). TryOpLowering keeps these in the copy a `return` runs and
// erases them from the rest.
#define RETURN_ONLY_ATTR_NAME "__return_only"

// Marks a `ts.CreateBoundFunction` whose `this` is a capture box built for it a moment earlier,
// rather than a receiver that belongs to somebody else. Only such a closure owns its `this`, and
// only it gets the type tag at CLOSURE_TYPE_INDEX - a bound method must not take ownership of
Expand Down
8 changes: 8 additions & 0 deletions tslang/include/TypeScript/MLIRLogic/MLIRGenContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,14 @@ struct GenContext
// scope nested further in releases the scopes between, and stops here
// (mlirGenReleaseOwnedOnUnwind).
bool isUnwindCleanupScope = false;
// Points at itself on the context that stands, in the walk outwards, for the scope around a
// `try` with a `finally` - the parent of its body's scope, and its catch clause's context. A
// `return` from inside the body or the catch releases the scopes up to here and no further:
// the finally still runs before the function returns, and it can read the scopes from here
// outwards, so the copy of the finally that a `return` runs releases them once it is done
// (mlirGenReleaseOwnedOnReturn). A pointer rather than a flag, so that it needs no clearing:
// every context copied from this one points at this one, not at itself.
const GenContext *finallyGuardScope = nullptr;
// out-of-band cancellation signal; mutable so stop() stays callable through the const& threading
mutable bool stopProcess = false;
mlir::SmallVector<std::unique_ptr<mlir::Diagnostic>> *postponedMessages = nullptr;
Expand Down
42 changes: 36 additions & 6 deletions tslang/lib/TypeScript/LowerToAffineLoops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1628,7 +1628,11 @@ struct TryOpLowering : public TsPattern<mlir_ts::TryOp>
// Erased from each later copy through its mapping, and from the region itself only once
// the last copy is made: the rewriter defers an erasure, so an op erased from the
// region still goes into every copy cloned from it afterwards.
//
// The releases a `return` left for after the finally (RETURN_ONLY_ATTR_NAME) the other
// way round: kept in the copy a `return` runs, and gone from every other.
SmallVector<Operation *> unwindOnlyOps;
SmallVector<Operation *> returnOnlyOps;
tryOp.getFinally().walk<mlir::WalkOrder::PreOrder>([&](Operation *op) {
if (isa<mlir_ts::TryOp>(op))
{
Expand All @@ -1640,28 +1644,53 @@ struct TryOpLowering : public TsPattern<mlir_ts::TryOp>
unwindOnlyOps.push_back(op);
}

if (op->hasAttr(RETURN_ONLY_ATTR_NAME))
{
returnOnlyOps.push_back(op);
}

return WalkResult::advance();
});

auto eraseUnwindOnlyOps = [&](const mlir::IRMapping &mapping) {
for (auto *op : unwindOnlyOps)
auto eraseOps = [&](ArrayRef<Operation *> ops, const mlir::IRMapping &mapping) {
for (auto *op : ops)
{
rewriter.eraseOp(mapping.lookup(op));
}
};

eraseOps(returnOnlyOps, finallyMapping);

// add clone for 'return'
if (returns.size() > 0)
{
mlir::IRMapping returnFinallyMapping;
rewriter.cloneRegionBefore(tryOp.getFinally(), *continuation->getParent(), continuation->getIterator(),
returnFinallyMapping);
propagateTsContextEntries(returnFinallyMapping);
eraseUnwindOnlyOps(returnFinallyMapping);
eraseOps(unwindOnlyOps, returnFinallyMapping);
auto returnFinallyBlockLast = continuation->getPrevNode();
rewriter.setInsertionPoint(returnFinallyBlockLast->getTerminator());
auto resultOpOfReturnFinallyBlock = cast<mlir_ts::ResultOp>(returnFinallyBlockLast->getTerminator());
rewriter.replaceOpWithNewOp<mlir_ts::ReturnOp>(resultOpOfReturnFinallyBlock);
auto returnOfFinally = rewriter.replaceOpWithNewOp<mlir_ts::ReturnOp>(resultOpOfReturnFinallyBlock);

// A `try` around this one lowered first and sent these returns to its own finally
// copy. The return this copy ends with goes there in their stead, so the outer
// finally still runs - pointing the returns at this copy alone skipped it - and
// releases the scopes it guards (mlirGenReleaseOwnedOnReturn).
mlir::Block *outerCleanup = nullptr;
for (auto retOp : returns)
{
auto retOpCleanup = tsContext->cleanup.lookup(retOp);
assert(!outerCleanup || !retOpCleanup || outerCleanup == retOpCleanup);
outerCleanup = outerCleanup ? outerCleanup : retOpCleanup;
}

if (outerCleanup)
{
tsContext->cleanup[returnOfFinally] = outerCleanup;
}

// if has returns we need to create return cleanup block
for (auto retOp : returns)
{
Expand All @@ -1688,7 +1717,8 @@ struct TryOpLowering : public TsPattern<mlir_ts::TryOp>
rewriter.cloneRegionBefore(tryOp.getFinally(), *continuation->getParent(),
continuation->getIterator(), jumpFinallyMapping);
propagateTsContextEntries(jumpFinallyMapping);
eraseUnwindOnlyOps(jumpFinallyMapping);
eraseOps(unwindOnlyOps, jumpFinallyMapping);
eraseOps(returnOnlyOps, jumpFinallyMapping);
auto jumpFinallyBlockLast = continuation->getPrevNode();
rewriter.setInsertionPoint(jumpFinallyBlockLast->getTerminator());
auto resultOpOfJumpFinallyBlock = cast<mlir_ts::ResultOp>(jumpFinallyBlockLast->getTerminator());
Expand All @@ -1713,7 +1743,7 @@ struct TryOpLowering : public TsPattern<mlir_ts::TryOp>
}
}

for (auto *op : unwindOnlyOps)
for (auto *op : llvm::concat<Operation *const>(unwindOnlyOps, returnOnlyOps))
{
rewriter.eraseOp(op);
}
Expand Down
38 changes: 34 additions & 4 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,13 @@ class MLIRGenImpl
return mlir::success();
}

// a `return` stops at a `try` whose `finally` still has to run: that finally's copy for
// the `return` releases the scopes from here outwards (mlirGenReleaseOwnedOnReturn)
if (disposeDepth == DisposeDepth::FullStack && genContext->finallyGuardScope == genContext)
{
return mlir::success();
}

if (genContext->ownedVars != nullptr)
{
releaseScopeOwnedVars(location, *genContext->ownedVars);
Expand Down Expand Up @@ -859,8 +866,10 @@ class MLIRGenImpl
return mlir::success();
}

// `onlyOnAttr` names the copies of a hand-written finally the releases are kept in -
// UNWIND_ONLY_ATTR_NAME or RETURN_ONLY_ATTR_NAME - or is empty for an ordinary release
void releaseScopeOwnedVars(mlir::Location location, const SmallVector<mlir::Value> &ownedVars,
bool unwindOnly = false)
StringRef onlyOnAttr = {})
{
// reverse declaration order, the order a scope is unwound in: a later local may
// hold the only other reference to what an earlier one points at
Expand All @@ -880,9 +889,9 @@ class MLIRGenImpl
releaseOp = builder.create<mlir_ts::ReleaseSlotOp>(location, storage);
}

if (releaseOp && unwindOnly)
if (releaseOp && !onlyOnAttr.empty())
{
releaseOp->setAttr(UNWIND_ONLY_ATTR_NAME, builder.getUnitAttr());
releaseOp->setAttr(onlyOnAttr, builder.getUnitAttr());
}
}
}
Expand Down Expand Up @@ -917,7 +926,28 @@ class MLIRGenImpl
{
if (genContext->ownedVars != nullptr)
{
releaseScopeOwnedVars(location, *genContext->ownedVars, unwindOnly);
releaseScopeOwnedVars(location, *genContext->ownedVars, unwindOnly ? UNWIND_ONLY_ATTR_NAME : "");
}
}
}

// The end of the copy of a hand-written `finally` that a `return` from its `try` body or catch
// clause runs (RETURN_ONLY_ATTR_NAME): the rest of that `return`'s scope exit. The `return`
// released the scopes inside the `try` and stopped at its finallyGuardScope, because the
// finally can still read every scope from there outwards - `let s = ...; try { return x; }
// finally { log(s); }` read `s` after the `return` had given it back.
//
// Walks outwards from the scope around the `try`, as a `return` does, and stops at the
// finallyGuardScope of the next `try` out with a `finally`: the `return` goes on through that
// finally's own copy (TryOpLowering chains them), which releases from there.
void mlirGenReleaseOwnedOnReturn(mlir::Location location, const GenContext *genContext)
{
for (; genContext != nullptr && genContext->finallyGuardScope != genContext;
genContext = genContext->parentBlockContext)
{
if (genContext->ownedVars != nullptr)
{
releaseScopeOwnedVars(location, *genContext->ownedVars, RETURN_ONLY_ATTR_NAME);
}
}
}
Expand Down
21 changes: 19 additions & 2 deletions tslang/lib/TypeScript/MLIRGenStatements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1467,6 +1467,17 @@ namespace mlirgen
tryGenContext.allocateScopeOwnedVarsOutsideOfOperation = true;
tryGenContext.currentOperation = tryOp;

// The scope around the `try` as its body and catch clause see it on the way out, when it
// has a `finally`: a `return` from either stops here, and the finally's copy for the
// `return` releases the rest (GenContext::finallyGuardScope). A copy of tryGenContext,
// because the finally itself is generated in tryGenContext, and a `return` in the
// finally is not one this finally runs after.
GenContext finallyGuardGenContext(tryGenContext);
if (tryStatementAST->finallyBlock)
{
finallyGuardGenContext.finallyGuardScope = &finallyGuardGenContext;
}

SmallVector<mlir::Type, 0> types;

/*auto *body =*/builder.createBlock(&tryOp.getBody(), {}, types);
Expand All @@ -1481,7 +1492,7 @@ namespace mlirgen
// prepare custom scope
SymbolTableScopeT varScope(symbolTable);
GenContext tryBodyGenContext(tryGenContext);
tryBodyGenContext.parentBlockContext = &tryGenContext;
tryBodyGenContext.parentBlockContext = &finallyGuardGenContext;

// as in mlirGen(Block): this is the block's own scope, so it is where a break or
// continue walking outwards stops
Expand Down Expand Up @@ -1549,10 +1560,11 @@ namespace mlirgen
// With a finally, an exception out of the catch clause unwinds into the finally's
// unwind copy, which releases the scopes around the `try` (below) - so a `throw` in
// the clause must stop short of them, as it does at a TryOp's body
GenContext catchGenContext(tryGenContext);
GenContext catchGenContext(finallyGuardGenContext);
if (tryStatementAST->finallyBlock)
{
catchGenContext.isUnwindCleanupScope = true;
catchGenContext.finallyGuardScope = &catchGenContext;
}

auto result = mlirGen(tryStatementAST->catchClause->block, catchGenContext);
Expand All @@ -1576,6 +1588,11 @@ namespace mlirgen
// TryOpLowering drops them from the copies normal completion, `return` and `break`
// run, where those scopes are still live.
mlirGenReleaseOwnedOnUnwind(location, &tryGenContext, /*unwindOnly=*/true);

// And the copy a `return` from the body or the catch clause runs: the rest of that
// `return`'s scope exit, which it left for after the finally (finallyGuardScope).
// Marked return-only, so TryOpLowering drops them from the other copies.
mlirGenReleaseOwnedOnReturn(location, &tryGenContext);
}

// terminator
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 @@ -338,6 +338,7 @@ tslang_add_test(NAME test-compile-00-switch-case-scope-exit COMMAND test-runner
tslang_add_test(NAME test-compile-00-using-dispose-order COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00using_dispose_order.ts")
tslang_add_test(NAME test-compile-00-using-unwind-outer-locals COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00using_unwind_outer_locals.ts")
tslang_add_test(NAME test-compile-00-try-unwind-outer-locals COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_unwind_outer_locals.ts")
tslang_add_test(NAME test-compile-00-try-return-through-finally COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_return_through_finally.ts")
tslang_add_test(NAME test-compile-00-vars COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00vars.ts")
tslang_add_test(NAME test-compile-00-var-bindings COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00var_bindings.ts")
tslang_add_test(NAME test-compile-00-globals COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00globals.ts")
Expand Down Expand Up @@ -883,6 +884,7 @@ tslang_add_test(NAME test-jit-00-switch-case-scope-exit COMMAND test-runner -jit
tslang_add_test(NAME test-jit-00-using-dispose-order COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00using_dispose_order.ts")
tslang_add_test(NAME test-jit-00-using-unwind-outer-locals COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00using_unwind_outer_locals.ts")
tslang_add_test(NAME test-jit-00-try-unwind-outer-locals COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_unwind_outer_locals.ts")
tslang_add_test(NAME test-jit-00-try-return-through-finally COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_return_through_finally.ts")
tslang_add_test(NAME test-jit-00-vars COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00vars.ts")
tslang_add_test(NAME test-jit-00-var-bindings COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00var_bindings.ts")
tslang_add_test(NAME test-jit-00-globals COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00globals.ts")
Expand Down Expand Up @@ -1839,6 +1841,7 @@ set(TSLANG_CORPUS
00using_dispose_order.ts
00using_unwind_outer_locals.ts
00try_unwind_outer_locals.ts
00try_return_through_finally.ts
00capture_in_new_arguments.ts
00cast_to_f32.ts
00catch_untyped_value.ts
Expand Down
Loading
Loading