summaryrefslogtreecommitdiff
path: root/source/slang/slang-lower-to-ir.cpp
diff options
context:
space:
mode:
authorJulius Ikkala <julius.ikkala@gmail.com>2025-05-23 22:27:37 +0300
committerGitHub <noreply@github.com>2025-05-23 12:27:37 -0700
commit57c3f938221c427b78da7087f8a832ba4a271a7c (patch)
treee9a6d26278dc1ad75b222ac4fc9b7a1d8449e576 /source/slang/slang-lower-to-ir.cpp
parentd108bfa677c70808b32bd77e93637ed34c19c75d (diff)
Implement throw & catch statements (#6916)
* Implement throw statement It already existed in the IR, so only parsing, checking and lowering was missing. * Initial catch implementation Likely very broken. * Error out when catch() isn't last in scope * Prevent accessing variables from scope preceding catch As those may actually not be available at that point. * Add IError and use it in Result type lowering * Add diagnostic tests * Allow caught throws in non-throw functions * Fix catch propagating between functions & SPIR-V merge issue * Add test for non-trivial error types * Fix MSVC build * Fix invalid value type from Result lowering * Also lower error handling in templates * Lower result types only after specialization * Attempt to disambiguate error enums by witness table * Revert matching by witness, types should be distinct too * Don't assert valueField when getting Result's error value It may not exist if the function returns void, but getting the error value is still legitimate. * Update tests for new error numbers & get rid of expected.txt * Change catch lowering to resemble breaking a loop ... To make SPIR-V happy. * Fix dead catch blocks and invalid cached dominator tree * More SPIR-V adjustment * Lower catch as two nested loops * Add defer interaction test and revert broken defer changes * Fix enum type when throwing literals * Cleanup and bikeshedding * Document error handling mechanism * Fix table of contents * Use boolean tag in Result<T, E> * Use anyValue storage for Result<T,E> * Remove IError * Fix formatting * Eradicate success values from docs and tests * Use parseModernParamDecl for catch parameter * Implement do-catch syntax * Implement catch-all * Fix formatting * Fix marshalling native calls that throw --------- Co-authored-by: Yong He <yonghe@outlook.com>
Diffstat (limited to 'source/slang/slang-lower-to-ir.cpp')
-rw-r--r--source/slang/slang-lower-to-ir.cpp168
1 files changed, 159 insertions, 9 deletions
diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp
index 9920075fe..ebcdaf1e1 100644
--- a/source/slang/slang-lower-to-ir.cpp
+++ b/source/slang/slang-lower-to-ir.cpp
@@ -555,6 +555,17 @@ struct AstOrIRType
explicit operator bool() { return astType || irType; }
};
+struct CatchHandler
+{
+ // 'nullptr' implies catch-all.
+ IRType* errorType = nullptr;
+
+ // Block of the handler statement. Takes a value of errorType as parameter.
+ IRBlock* errorHandler = nullptr;
+
+ CatchHandler* prev = nullptr;
+};
+
struct IRGenContext
{
ASTBuilder* astBuilder;
@@ -599,6 +610,9 @@ struct IRGenContext
// The current scope end for use with `defer`.
IRBlock* scopeEndBlock = nullptr;
+ // A chain of nested `catch` handlers for `try` and `throw.
+ CatchHandler* catchHandler = nullptr;
+
// Callback function to call when after lowering a type.
std::function<IRType*(IRGenContext* context, Type* type, IRType* irType)> lowerTypeCallback =
nullptr;
@@ -754,6 +768,19 @@ int32_t getIntrinsicOp(Decl* decl, IntrinsicOpModifier* intrinsicOpMod)
return int32_t(irOp);
}
+static CatchHandler findErrorHandler(IRGenContext* context, IRType* type)
+{
+ for (auto handler = context->catchHandler; handler != nullptr;
+ handler = context->catchHandler->prev)
+ {
+ if (!handler->errorType || handler->errorType == type)
+ {
+ return *handler;
+ }
+ }
+ return CatchHandler();
+}
+
struct TryClauseEnvironment
{
TryClauseType clauseType = TryClauseType::None;
@@ -809,18 +836,28 @@ LoweredValInfo emitCallToVal(
case TryClauseType::Standard:
{
auto callee = getSimpleVal(context, funcVal);
- auto succBlock = builder->createBlock();
- auto failBlock = builder->createBlock();
auto funcType = as<IRFuncType>(callee->getDataType());
auto throwAttr = funcType->findAttr<IRFuncThrowTypeAttr>();
assert(throwAttr);
+
+ auto handler = findErrorHandler(context, throwAttr->getErrorType());
+ auto succBlock = builder->createBlock();
+ auto failBlock =
+ handler.errorHandler ? handler.errorHandler : builder->createBlock();
+
auto voidType = builder->getVoidType();
builder->emitTryCallInst(voidType, succBlock, failBlock, callee, argCount, args);
- builder->insertBlock(failBlock);
- auto errParam = builder->emitParam(throwAttr->getErrorType());
- builder->emitThrow(errParam);
builder->insertBlock(succBlock);
auto value = builder->emitParam(type);
+
+ if (!handler.errorHandler)
+ {
+ // We have to create a default fail block, which just re-throws.
+ builder->insertBlock(failBlock);
+ auto errParam = builder->emitParam(throwAttr->getErrorType());
+ builder->emitThrow(errParam);
+ builder->setInsertInto(succBlock);
+ }
return LoweredValInfo::simple(value);
}
break;
@@ -1982,8 +2019,9 @@ struct ValLoweringVisitor : ValVisitor<ValLoweringVisitor, LoweredValInfo, Lower
else
{
auto errorType = lowerType(context, type->getErrorType());
+ IRInst* operands[] = {errorType};
auto irThrowFuncTypeAttribute =
- getBuilder()->getAttr(kIROp_FuncThrowTypeAttr, 1, (IRInst**)&errorType);
+ getBuilder()->getAttr(kIROp_FuncThrowTypeAttr, 1, operands);
return getBuilder()->getFuncType(
paramCount,
paramTypes.getBuffer(),
@@ -3449,9 +3487,9 @@ void _lowerFuncDeclBaseTypeInfo(
if (!getErrorCodeType(context->astBuilder, declRef)
->equals(context->astBuilder->getBottomType()))
{
- auto errorType = lowerType(context, getErrorCodeType(context->astBuilder, declRef));
- IRAttr* throwTypeAttr = nullptr;
- throwTypeAttr = builder->getAttr(kIROp_FuncThrowTypeAttr, 1, (IRInst**)&errorType);
+ auto irErrorType = lowerType(context, getErrorCodeType(context->astBuilder, declRef));
+ IRInst* operands[] = {irErrorType};
+ IRAttr* throwTypeAttr = builder->getAttr(kIROp_FuncThrowTypeAttr, 1, operands);
outInfo.type = builder->getFuncType(
paramTypes.getCount(),
paramTypes.getBuffer(),
@@ -6671,6 +6709,117 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor>
builder->setInsertInto(mergeBlock);
}
+ void visitThrowStmt(ThrowStmt* stmt)
+ {
+ auto builder = getBuilder();
+ startBlockIfNeeded(stmt);
+
+ auto loweredExpr = lowerRValueExpr(context, stmt->expression);
+ auto loweredVal = getSimpleVal(context, loweredExpr);
+ auto throwType = lowerType(context, stmt->expression->type);
+
+ CatchHandler handler;
+ if (loweredVal && throwType)
+ {
+ handler = findErrorHandler(context, throwType);
+ }
+
+ if (handler.errorHandler)
+ {
+ builder->emitBranch(handler.errorHandler, 1, &loweredVal);
+ }
+ else
+ {
+ builder->emitThrow(getSimpleVal(context, loweredExpr));
+ }
+ }
+
+ void visitCatchStmt(CatchStmt* stmt)
+ {
+ auto builder = getBuilder();
+ startBlockIfNeeded(stmt);
+
+ // The mental model here is that the below Catch statement:
+ //
+ // let val = try MayThrowFunc();
+ // // Do stuff with val
+ // catch(err: Error)
+ // {
+ // catchBlock(err);
+ // }
+ //
+ // lowers similarly to:
+ //
+ // handlerLoop: for(;;)
+ // {
+ // E err; // Actually just a parameter for the catchBlock, not a real variable.
+ // bodyLoop: for(;;)
+ // {
+ // // Body goes here
+ // Result<T, E> r = mayThrowFunc();
+ // if(isResultError(r))
+ // {
+ // err = r.error;
+ // break bodyLoop;
+ // }
+ // let val = r.getSuccessValue();
+ // // Do stuff with val
+ // break handlerLoop;
+ // }
+ // catchBlock(err);
+ // break handlerLoop;
+ // }
+ //
+ // This approach allows for it to generate valid SPIR-V. Just jumping
+ // around with unstructured conditional jumps doesn't work there.
+
+ IRBlock* handlerLoopHead = createBlock();
+ IRBlock* handlerBreakLabel = createBlock();
+ IRBlock* bodyLoopHead = createBlock();
+ IRBlock* bodyBreakLabel = createBlock();
+
+ builder->emitLoop(handlerLoopHead, handlerBreakLabel, handlerLoopHead);
+ insertBlock(handlerLoopHead);
+
+ builder->emitLoop(bodyLoopHead, bodyBreakLabel, bodyLoopHead);
+ insertBlock(bodyLoopHead);
+
+ CatchHandler catchHandler;
+ catchHandler.errorType =
+ stmt->errorVar ? lowerType(context, stmt->errorVar->getType()) : nullptr;
+ catchHandler.errorHandler = bodyBreakLabel;
+ catchHandler.prev = context->catchHandler;
+ context->catchHandler = &catchHandler;
+
+ // Note that the tryBody doesn't actually have to have it's own scope or
+ // block. If there's a `defer` in the tryBody, it can run after the
+ // catch statement.
+ lowerStmt(context, stmt->tryBody);
+
+ // Put break; at the end of the body if there's nothing else there yet.
+ // This prevents the catch handler from running.
+ emitBranchIfNeeded(handlerBreakLabel);
+
+ context->catchHandler = catchHandler.prev;
+
+ insertBlock(bodyBreakLabel);
+
+ if (catchHandler.errorType)
+ {
+ auto irParam = builder->emitParam(catchHandler.errorType);
+ auto paramVal = LoweredValInfo::simple(irParam);
+ context->setGlobalValue(stmt->errorVar, paramVal);
+ }
+
+ IRBlock* prevScopeEndBlock = pushScopeBlock(handlerBreakLabel);
+ lowerStmt(context, stmt->handleBody);
+ popScopeBlock(prevScopeEndBlock, true);
+
+ emitBranchIfNeeded(handlerBreakLabel);
+
+ insertBlock(handlerBreakLabel);
+ }
+
void visitDiscardStmt(DiscardStmt* stmt)
{
startBlockIfNeeded(stmt);
@@ -8536,6 +8685,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
subContextStorage.returnDestination = LoweredValInfo();
subContextStorage.lowerTypeCallback = nullptr;
+ subContextStorage.catchHandler = nullptr;
}
IRBuilder* getBuilder() { return &subBuilderStorage; }