diff options
| author | Julius Ikkala <julius.ikkala@gmail.com> | 2025-05-23 22:27:37 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-05-23 12:27:37 -0700 |
| commit | 57c3f938221c427b78da7087f8a832ba4a271a7c (patch) | |
| tree | e9a6d26278dc1ad75b222ac4fc9b7a1d8449e576 /source/slang/slang-lower-to-ir.cpp | |
| parent | d108bfa677c70808b32bd77e93637ed34c19c75d (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.cpp | 168 |
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; } |
