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/TypeScriptPassContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ struct TSContext
landingBlockOf.beginRun();
leavesCatch.beginRun();
catchPadIdOf.beginRun();
finallyPadIdOf.beginRun();
inParentTryFinallyPad.beginRun();
}

// options
Expand Down Expand Up @@ -161,6 +163,11 @@ struct TSContext
// catch clause can name it as the funclet they are nested in - see tagPad in TryOpLowering
// and ts.internal.eh_pad in Win32ExceptionPass. Ids are unique within the module.
OpSideTable<int> catchPadIdOf{"catchPadIdOf"};
// Windows: the same for the pad a try's finally is entered through on the exceptional path,
// and whether a try sits in that copy of its parentTryOp's finally - the one copy that runs
// inside the pad's funclet, where the other copies (normal exit, return, break) do not.
OpSideTable<int> finallyPadIdOf{"finallyPadIdOf"};
OpSideTable<bool> inParentTryFinallyPad{"inParentTryFinallyPad"};
int nextCatchPadId = 0;
mlir::Block *returnBlock;
};
Expand Down
134 changes: 122 additions & 12 deletions tslang/lib/TypeScript/LowerToAffineLoops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1347,8 +1347,13 @@ struct TryOpLowering : public TsPattern<mlir_ts::TryOp>
// Windows: which catch funclet this try's pads are nested in - the catch clause of the
// nearest try around it that holds it in its catch, through any try bodies in between.
// Win32ExceptionPass cannot tell that from the IR: a pad reached from inside a catch is
// either a try nested in it or where exceptions leave it, and the two look alike. A try
// in a finally is left at the top, as every pad was before.
// either a try nested in it or where exceptions leave it, and the two look alike.
//
// The same goes for a try in the copy of a finally that runs on the exceptional path: it
// runs inside that finally's pad, and its own pads have to be nested in it. Left at the
// top, they ended the finally's region in the pass before its end was reached, and the
// pass crashed on the region with no end. A try in a copy of a finally on a normal path
// is left at the top, as before.
auto funcletParentId = 0;
if (tsContext->compileOptions.isWindows)
{
Expand All @@ -1361,6 +1366,12 @@ struct TryOpLowering : public TsPattern<mlir_ts::TryOp>
break;
}

if (tsContext->inParentTryFinallyPad.lookup(child))
{
funcletParentId = tsContext->finallyPadIdOf.lookup(parent);
break;
}

if (!tsContext->inParentTryBody.lookup(child))
{
break;
Expand All @@ -1369,15 +1380,15 @@ struct TryOpLowering : public TsPattern<mlir_ts::TryOp>
}

auto catchPadId = 0;
auto tagPad = [&](mlir_ts::LandingPadOp landingPadOp, bool isCatchPad) {
auto tagPad = [&](mlir_ts::LandingPadOp landingPadOp, int padId) {
if (!tsContext->compileOptions.isWindows)
{
return;
}

if (isCatchPad && catchPadId)
if (padId)
{
landingPadOp->setAttr(EH_PAD_ID_ATTR_NAME, rewriter.getI32IntegerAttr(catchPadId));
landingPadOp->setAttr(EH_PAD_ID_ATTR_NAME, rewriter.getI32IntegerAttr(padId));
}

if (funcletParentId)
Expand Down Expand Up @@ -1427,6 +1438,7 @@ struct TryOpLowering : public TsPattern<mlir_ts::TryOp>
tsContext->parentTryOp[op] = tryOp.getOperation();
tsContext->inParentTryBody[op] = inBody;
tsContext->inParentTryCatch[op] = inCatch;
tsContext->inParentTryFinallyPad[op] = false;
}
};
inBody = true;
Expand Down Expand Up @@ -1473,6 +1485,32 @@ struct TryOpLowering : public TsPattern<mlir_ts::TryOp>
collectEscapingJumps(tryOp.getBody(), escapingJumpsBody);
collectEscapingJumps(tryOp.getCatches(), escapingJumpsCatches);

// The copy of the finally on the exceptional path is entered through a cleanup pad, which
// can do two things a finally may need only from a catch:
// - return to normal flow: a `return`, or a break/continue out of the try, ends the
// exception there and then (`finally { return s; }` swallows it), where a cleanup can
// only carry on unwinding. On Windows the funclet had no way out; on the Itanium path
// an exception with only cleanups to run is never unwound at all - the search phase
// finds no handler and the program terminates before the finally runs.
// - hold pads of its own (Windows): a try in the finally. LLVM numbers no EH state for a
// pad in a cleanup funclet (the MSVC personality does not allow one), so an exception
// thrown in that try was never caught there, and the process terminated.
// Such a finally is entered through a catch-all pad instead, which rethrows at its end
// (see windowsFinallyCatches and linuxFinallyCatches).
mlir::SmallVector<Operation *> escapingOpsFinally;
auto finallyHasTry = false;
tryOp.getFinally().walk([&](Operation *op) {
if (isa<mlir_ts::ReturnOp, mlir_ts::ReturnValOp>(op) ||
(isa<mlir_ts::BreakOp, mlir_ts::ContinueOp>(op) && tsContext->jumps.contains(op)))
{
escapingOpsFinally.push_back(op);
}
else if (isa<mlir_ts::TryOp>(op))
{
finallyHasTry = true;
}
});

// inline structure
OpBuilder::InsertionGuard guard(rewriter);
mlir::Block *currentBlock = rewriter.getInsertionBlock();
Expand Down Expand Up @@ -1583,6 +1621,12 @@ struct TryOpLowering : public TsPattern<mlir_ts::TryOp>
mlir::Block *finallyBlock = nullptr;
mlir::Block *finallyBlockLast = nullptr;
mlir::Block *exitBlockLast = nullptr;
auto finallyPadId = 0;
auto windowsFinallyCatches =
tsContext->compileOptions.isWindows && finallyHasOps && (!escapingOpsFinally.empty() || finallyHasTry);
// with a try around it in the function the Itanium finally pad is a catch-all already
auto linuxFinallyCatches = !tsContext->compileOptions.isWindows && finallyHasOps && !enclosingHandler &&
!escapingOpsFinally.empty();
if (finallyHasOps)
{
// cloneRegionBefore makes brand-new Operation* instances, so any tsContext
Expand Down Expand Up @@ -1614,6 +1658,27 @@ struct TryOpLowering : public TsPattern<mlir_ts::TryOp>
rewriter.cloneRegionBefore(tryOp.getFinally(), *continuation->getParent(), continuation->getIterator(),
finallyMapping);
propagateTsContextEntries(finallyMapping);
if (tsContext->compileOptions.isWindows)
{
// this copy is the exceptional path's, which runs inside the finally's pad
finallyPadId = ++tsContext->nextCatchPadId;
tsContext->finallyPadIdOf[tryOp.getOperation()] = finallyPadId;
for (auto &[oldOp, newOp] : finallyMapping.getOperationMap())
{
if (isa<mlir_ts::TryOp>(newOp))
{
tsContext->inParentTryFinallyPad[newOp] = true;
}
}
}

// the catch-all pad a finally that can leave is entered through (on either path) has
// to be ended on the way out, as a catch clause's `return` ends it
for (auto op : escapingOpsFinally)
{
tsContext->unwind[finallyMapping.lookup(op)] = beforeFinallyBlock->getNextNode();
}

finallyBlock = beforeFinallyBlock->getNextNode();
tsContext->finallyBlockOf[tryOp.getOperation()] = finallyBlock;
finallyBlockLast = continuation->getPrevNode();
Expand Down Expand Up @@ -1770,7 +1835,8 @@ struct TryOpLowering : public TsPattern<mlir_ts::TryOp>

mlir::Value catchAll;
if (enclosingHandler && finallyHasOps || linuxHasCleanups && rttih.hasType() ||
linuxCleanupOnlyChainsToParent || linuxTypedCatchChains)
linuxCleanupOnlyChainsToParent || linuxTypedCatchChains || windowsFinallyCatches ||
linuxFinallyCatches)
{
catchAll = (mlir::Value)rewriter.create<mlir_ts::NullOp>(loc, mth.getNullType());
}
Expand Down Expand Up @@ -1802,7 +1868,7 @@ struct TryOpLowering : public TsPattern<mlir_ts::TryOp>

auto landingPadCleanupOp = rewriter.create<mlir_ts::LandingPadOp>(
loc, rttih.getLandingPadType(), rewriter.getBoolAttr(true), ValueRange{undefArrayValue});
tagPad(landingPadCleanupOp, false);
tagPad(landingPadCleanupOp, 0);
auto beginCleanupCallInfo = rewriter.create<mlir_ts::BeginCleanupOp>(loc);

rewriter.setInsertionPoint(cleanupBlockLast->getTerminator());
Expand Down Expand Up @@ -1854,7 +1920,7 @@ struct TryOpLowering : public TsPattern<mlir_ts::TryOp>

auto landingPadOp = rewriter.create<mlir_ts::LandingPadOp>(loc, rttih.getLandingPadType(),
rewriter.getBoolAttr(false), catchTypes);
tagPad(landingPadOp, true);
tagPad(landingPadOp, catchPadId);

// On Windows a typed catch that does not match is left by the runtime, through the
// catchswitch's unwind edge - but Win32ExceptionPass can only point that edge at the
Expand Down Expand Up @@ -1936,13 +2002,35 @@ struct TryOpLowering : public TsPattern<mlir_ts::TryOp>
} while (false);
}

if (tsContext->compileOptions.isWindows)
if (windowsFinallyCatches)
{
// see escapingOpsFinally: a catch-all, which a `return` in it ends; run to its
// end, it ends the same way a cleanup does, and Win32ExceptionPass rethrows
// there from the catch (a catch region ending in `resume`)
rewriter.setInsertionPointToStart(finallyBlock);

auto landingPadCatchAllOp = rewriter.create<mlir_ts::LandingPadOp>(
loc, rttih.getLandingPadType(), rewriter.getBoolAttr(false), ValueRange{catchAll});
tagPad(landingPadCatchAllOp, finallyPadId);
rewriter.create<mlir_ts::BeginCatchOp>(loc, mth.getOpaqueType(), landingPadCatchAllOp);

rewriter.setInsertionPoint(finallyBlockLast->getTerminator());
mlir::SmallVector<mlir::Block *> unwindDests;
if (enclosingHandler)
{
unwindDests.push_back(enclosingHandler);
}

auto resultOpFinally = cast<mlir_ts::ResultOp>(finallyBlockLast->getTerminator());
rewriter.replaceOpWithNewOp<mlir_ts::EndCleanupOp>(resultOpFinally, landingPadCatchAllOp, unwindDests);
}
else if (tsContext->compileOptions.isWindows)
{
rewriter.setInsertionPointToStart(finallyBlock);

auto landingPadCleanupOp = rewriter.create<mlir_ts::LandingPadOp>(
loc, rttih.getLandingPadType(), rewriter.getBoolAttr(true), ValueRange{undefArrayValue});
tagPad(landingPadCleanupOp, false);
tagPad(landingPadCleanupOp, finallyPadId);
auto beginCleanupCallInfo = rewriter.create<mlir_ts::BeginCleanupOp>(loc);

rewriter.setInsertionPoint(finallyBlockLast->getTerminator());
Expand All @@ -1957,7 +2045,29 @@ struct TryOpLowering : public TsPattern<mlir_ts::TryOp>
}
else
{
if (!enclosingHandler)
if (linuxFinallyCatches)
{
// see escapingOpsFinally: a catch-all, which a `return` in it ends; run to
// its end, it rethrows - the shape the enclosingHandler case below has
rewriter.setInsertionPointToStart(linuxHasCleanupsForFinally ? cleanupBlock : finallyBlock);

auto landingPadCatchAllOp = rewriter.create<mlir_ts::LandingPadOp>(
loc, rttih.getLandingPadType(), rewriter.getBoolAttr(false), ValueRange{catchAll});

if (linuxHasCleanupsForFinally)
{
rewriter.setInsertionPointToStart(finallyBlock);
}

rewriter.create<mlir_ts::BeginCatchOp>(loc, mth.getOpaqueType(), landingPadCatchAllOp);

rewriter.setInsertionPoint(finallyBlockLast->getTerminator());
auto nullVal = rewriter.create<mlir_ts::NullOp>(loc, mth.getNullType());

auto resultOpOfFinally = cast<mlir_ts::ResultOp>(finallyBlockLast->getTerminator());
rewriter.replaceOpWithNewOp<mlir_ts::ThrowOp>(resultOpOfFinally, nullVal);
}
else if (!enclosingHandler)
{
rewriter.setInsertionPointToStart(linuxHasCleanupsForFinally ? cleanupBlock : finallyBlock);

Expand Down
43 changes: 42 additions & 1 deletion tslang/lib/TypeScriptExceptionPass/Win32ExceptionPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,16 @@ struct Win32ExceptionPassCode
}
}

// a region the scan found no end for cannot be made a funclet; say so rather than crash
for (auto &catchRegion : catchRegionsWorkSet)
{
if (!catchRegion.end)
{
report_fatal_error(Twine("Win32ExceptionPass: no end found for the exception handling region of a landing pad in '") +
F.getName() + "'");
}
}

chooseUnwindInfoOps(catchRegionsWorkSet);

// create begin of catch block (a parent comes before the regions nested in it)
Expand Down Expand Up @@ -538,7 +548,38 @@ struct Win32ExceptionPassCode

if (catchRegion.isCatch())
{
if (!endInvoke)
if (isa<ResumeInst>(I))
{
// A catch-all that ends the way a cleanup does: the copy of a `finally` on the
// exceptional path, which TryOpLowering enters through a catch when it holds a
// try or a `return` (a cleanup funclet can do neither). Run to its end, the
// finally hands the exception on: rethrow it from inside the funclet, to the
// handler the region unwinds to, or to the caller.
llvm::SmallVector<OperandBundleDef> opBundle;
opBundle.emplace_back(OperandBundleDef("funclet", catchRegion.catchPad));

auto throwFunc = getThrowFn(Ctx, F.getParent());
auto nullI8Ptr = ConstantPointerNull::get(PointerType::get(Ctx, 0));
auto nullTI = ConstantPointerNull::get(cast<llvm::PointerType>(throwFunc.getFunctionType()->params()[1]));

// With a handler to go on to, the end-of-catch marker is an invoke that ends
// a block of its own, and is removed with the rest: the rethrow takes its place.
auto *rethrowAt = isa_and_nonnull<InvokeInst>(catchRegion.cxaEndCatch) ? catchRegion.cxaEndCatch : I;
auto *II = catchRegion.unwindInfoOp;
if (auto *handlerPad = II ? II->getUnwindDest() : nullptr)
{
auto *unreachableBB = BasicBlock::Create(Ctx, "rethrow.cont", &F);
new UnreachableInst(Ctx, unreachableBB);
InvokeInst::Create(throwFunc, unreachableBB, handlerPad, {nullI8Ptr, nullTI}, opBundle, "",
rethrowAt->getIterator());
}
else
{
CallInst::Create(throwFunc, {nullI8Ptr, nullTI}, opBundle, "", rethrowAt->getIterator());
new UnreachableInst(Ctx, rethrowAt->getIterator());
}
}
else if (!endInvoke)
{
assert(catchRegion.catchPad);
auto CR = CatchReturnInst::Create(catchRegion.catchPad, retBlock, BI ? BI->getParent() : I->getParent());
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 @@ -651,6 +651,7 @@ tslang_add_test(NAME test-compile-00-try-catch-return COMMAND test-runner "${PRO
tslang_add_test(NAME test-compile-00-try-finally COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_finally.ts")
tslang_add_test(NAME test-compile-01-try-finally COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/01try_finally.ts")
tslang_add_test(NAME test-compile-00-try-finally-return COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_finally_return.ts")
tslang_add_test(NAME test-compile-00-try-finally-funclet COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_finally_funclet.ts")
tslang_add_test(NAME test-compile-00-try-finally-break-continue COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_finally_break_continue.ts")
tslang_add_test(NAME test-compile-00-try-catch-rethrow COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_catch_rethrow.ts")
tslang_add_test(NAME test-compile-00-try-catch-mismatch-rethrow COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_catch_mismatch_rethrow.ts")
Expand Down Expand Up @@ -1193,6 +1194,7 @@ tslang_add_test(NAME test-jit-00-try-catch-return COMMAND test-runner -jit "${PR
tslang_add_test(NAME test-jit-00-try-finally COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_finally.ts")
tslang_add_test(NAME test-jit-01-try-finally COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/01try_finally.ts")
tslang_add_test(NAME test-jit-00-try-finally-return COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_finally_return.ts")
tslang_add_test(NAME test-jit-00-try-finally-funclet COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_finally_funclet.ts")
tslang_add_test(NAME test-jit-00-try-finally-break-continue COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_finally_break_continue.ts")
tslang_add_test(NAME test-jit-00-try-catch-rethrow COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00try_catch_rethrow.ts")
# pre-existing JIT limitation (reproduces on unmodified main too): the JIT's
Expand Down Expand Up @@ -2099,6 +2101,7 @@ set(TSLANG_CORPUS
00try_catch.ts
00try_finally_break_continue.ts
00try_finally_return.ts
00try_finally_funclet.ts
00try_in_catch_with_finally.ts
00try_finally.ts
00try_using_catch.ts
Expand Down
Loading
Loading