| //===--- CIRGenCleanup.cpp - Bookkeeping and code emission for cleanups ---===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file contains code dealing with the IR generation for cleanups |
| // and related information. |
| // |
| // A "cleanup" is a piece of code which needs to be executed whenever |
| // control transfers out of a particular scope. This can be |
| // conditionalized to occur only on exceptional control flow, only on |
| // normal control flow, or both. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "CIRGenCleanup.h" |
| #include "CIRGenFunction.h" |
| |
| #include "clang/AST/RecursiveASTVisitor.h" |
| #include "clang/CIR/MissingFeatures.h" |
| |
| using namespace clang; |
| using namespace clang::CIRGen; |
| |
| namespace { |
| /// Return true if the expression tree contains an AbstractConditionalOperator |
| /// (ternary ?:), which is the only construct whose CIR codegen calls |
| /// ConditionalEvaluation::beginEvaluation() and thus causes cleanups to be |
| /// deferred via pushFullExprCleanup. Logical &&/|| do NOT call |
| /// beginEvaluation(); their branch-local cleanups are handled by LexicalScope. |
| class ConditionalEvaluationFinder |
| : public RecursiveASTVisitor<ConditionalEvaluationFinder> { |
| bool foundConditional = false; |
| |
| public: |
| bool found() const { return foundConditional; } |
| |
| bool VisitAbstractConditionalOperator(AbstractConditionalOperator *) { |
| foundConditional = true; |
| return false; |
| } |
| |
| // Don't cross evaluation-context boundaries. |
| bool TraverseLambdaExpr(LambdaExpr *) { return true; } |
| bool TraverseBlockExpr(BlockExpr *) { return true; } |
| bool TraverseStmtExpr(StmtExpr *) { return true; } |
| }; |
| } // namespace |
| |
| //===----------------------------------------------------------------------===// |
| // CIRGenFunction cleanup related |
| //===----------------------------------------------------------------------===// |
| |
| /// Emits all the code to cause the given temporary to be cleaned up. |
| void CIRGenFunction::emitCXXTemporary(const CXXTemporary *temporary, |
| QualType tempType, Address ptr) { |
| pushDestroy(NormalAndEHCleanup, ptr, tempType, destroyCXXObject); |
| } |
| |
| Address CIRGenFunction::createCleanupActiveFlag() { |
| assert(isInConditionalBranch()); |
| mlir::Location loc = builder.getUnknownLoc(); |
| |
| // Place the alloca in the function entry block so it dominates everything, |
| // including both regions of any enclosing cir.cleanup.scope. We can't rely |
| // on the default curLexScope path because we may be inside a ternary branch |
| // whose LexicalScope would capture the alloca. |
| Address active = createTempAllocaWithoutCast( |
| builder.getBoolTy(), CharUnits::One(), loc, "cleanup.cond", |
| /*arraySize=*/nullptr, |
| builder.getBestAllocaInsertPoint(getCurFunctionEntryBlock())); |
| |
| // Initialize to false before the outermost conditional. |
| { |
| mlir::OpBuilder::InsertionGuard guard(builder); |
| builder.restoreInsertionPoint(outermostConditional->getInsertPoint()); |
| builder.createFlagStore(loc, false, active.getPointer()); |
| } |
| |
| // Set to true at the current location (inside the conditional branch). |
| builder.createFlagStore(loc, true, active.getPointer()); |
| |
| return active; |
| } |
| |
| void CIRGenFunction::initFullExprCleanup() { |
| initFullExprCleanupWithFlag(createCleanupActiveFlag()); |
| } |
| |
| void CIRGenFunction::initFullExprCleanupWithFlag(Address activeFlag) { |
| EHCleanupScope &cleanup = cast<EHCleanupScope>(*ehStack.begin()); |
| assert(!cleanup.hasActiveFlag() && "cleanup already has active flag?"); |
| cleanup.setActiveFlag(activeFlag); |
| |
| cleanup.setTestFlagInNormalCleanup(cleanup.isNormalCleanup()); |
| cleanup.setTestFlagInEHCleanup(cleanup.isEHCleanup()); |
| } |
| |
| CIRGenFunction::FullExprCleanupScope::FullExprCleanupScope(CIRGenFunction &cgf, |
| const Expr *subExpr) |
| : cgf(cgf), cleanups(cgf), scope(nullptr), |
| deferredCleanupStackSize(cgf.deferredConditionalCleanupStack.size()) { |
| |
| assert(subExpr && "ExprWithCleanups always has a sub-expression"); |
| ConditionalEvaluationFinder finder; |
| finder.TraverseStmt(const_cast<Expr *>(subExpr)); |
| if (finder.found()) { |
| mlir::Location loc = cgf.builder.getUnknownLoc(); |
| cir::CleanupKind cleanupKind = cgf.getLangOpts().Exceptions |
| ? cir::CleanupKind::All |
| : cir::CleanupKind::Normal; |
| scope = cir::CleanupScopeOp::create( |
| cgf.builder, loc, cleanupKind, |
| /*bodyBuilder=*/ |
| [&](mlir::OpBuilder &b, mlir::Location loc) {}, |
| /*cleanupBuilder=*/ |
| [&](mlir::OpBuilder &b, mlir::Location loc) {}); |
| cgf.builder.setInsertionPointToEnd(&scope.getBodyRegion().front()); |
| } |
| } |
| |
| /// If the alloca that backs \p addr is currently nested inside the body |
| /// region of \p scope, hoist it, and any cast chain leading to it, out of the |
| // scope so the alloca dominates the scope's sibling cleanup region. |
| static void hoistAllocaOutOfCleanupScope(CIRGenFunction &cgf, Address addr, |
| cir::CleanupScopeOp scope) { |
| cir::AllocaOp alloca = addr.getUnderlyingAllocaOp(); |
| if (!alloca) |
| return; |
| |
| // If the alloca is not contained within the cleanup scope we're currently |
| // proccessing we don't need to hoist it. |
| auto cur = alloca->getParentOfType<cir::CleanupScopeOp>(); |
| while (cur && cur != scope) |
| cur = cur->getParentOfType<cir::CleanupScopeOp>(); |
| if (cur != scope) |
| return; |
| |
| // Place the alloca at the canonical alloca insertion point of the block |
| // containing the cleanup scope op, so it groups with any preceding |
| // allocas / labels and dominates both the body and cleanup regions. |
| mlir::Block *parentBlock = scope->getBlock(); |
| mlir::OpBuilder::InsertPoint ip = |
| CIRGenBuilderTy::getBestAllocaInsertPoint(parentBlock); |
| alloca->moveBefore(parentBlock, ip.getPoint()); |
| |
| // Move any cast chain that consumes the alloca's result to immediately after |
| // the alloca, so the address used by the deferred cleanup also dominates the |
| // cleanup region. We walk down the chain starting from the alloca's user |
| // that the Address was built from. This is very conservative. In practice, |
| // we should only ever see alloca or address_space(alloca) operations here. |
| mlir::Value ptr = addr.getPointer(); |
| llvm::SmallVector<cir::CastOp> casts; |
| for (mlir::Operation *cur = ptr.getDefiningOp(); cur && cur != alloca;) { |
| auto cast = mlir::dyn_cast<cir::CastOp>(cur); |
| if (!cast) |
| break; |
| casts.push_back(cast); |
| cur = cast.getSrc().getDefiningOp(); |
| } |
| // Move casts in source order (closest to the alloca first). |
| mlir::Operation *prev = alloca; |
| for (cir::CastOp cast : llvm::reverse(casts)) { |
| cast->moveAfter(prev); |
| prev = cast; |
| } |
| } |
| |
| void CIRGenFunction::FullExprCleanupScope::exit( |
| ArrayRef<mlir::Value *> valuesToReload) { |
| assert(!exited && "FullExprCleanupScope::exit called twice"); |
| exited = true; |
| |
| size_t oldSize = deferredCleanupStackSize; |
| bool hasDeferredCleanups = |
| cgf.deferredConditionalCleanupStack.size() > oldSize; |
| |
| if (!scope) { |
| cgf.deferredConditionalCleanupStack.truncate(oldSize); |
| cleanups.forceCleanup(valuesToReload); |
| return; |
| } |
| |
| // Spill any values that callers need after the scope is closed. |
| SmallVector<Address> tempAllocas; |
| for (mlir::Value *valPtr : valuesToReload) { |
| mlir::Value val = *valPtr; |
| if (!val) { |
| tempAllocas.push_back(Address::invalid()); |
| continue; |
| } |
| Address temp = cgf.createDefaultAlignTempAlloca(val.getType(), val.getLoc(), |
| "tmp.exprcleanup"); |
| tempAllocas.push_back(temp); |
| cgf.builder.createStore(val.getLoc(), val, temp); |
| } |
| |
| // Pop any EH cleanups that were pushed during the expression but leave |
| // any lifetime-extended cleanups so that they can be promoted to the EH |
| // stack after we've finished emitting any deferred cleanups. |
| cleanups.forceCleanupExceptLifetimeExtended(); |
| |
| // Make sure the cleanup scope body region has a terminator. |
| { |
| mlir::OpBuilder::InsertionGuard guard(cgf.builder); |
| mlir::Block &lastBodyBlock = scope.getBodyRegion().back(); |
| cgf.builder.setInsertionPointToEnd(&lastBodyBlock); |
| if (lastBodyBlock.empty() || |
| !lastBodyBlock.back().hasTrait<mlir::OpTrait::IsTerminator>()) |
| cgf.builder.createYield(scope.getLoc()); |
| } |
| |
| // Each deferred conditional cleanup will reference its addr from the |
| // sibling cleanup region we are about to fill. If the alloca that backs |
| // that addr was created inside this scope's body region, hoist it out so it |
| // dominates the cleanup region. |
| if (hasDeferredCleanups) { |
| for (const PendingCleanupEntry &entry : |
| llvm::make_range(cgf.deferredConditionalCleanupStack.begin() + oldSize, |
| cgf.deferredConditionalCleanupStack.end())) { |
| hoistAllocaOutOfCleanupScope(cgf, entry.addr, scope); |
| } |
| } |
| |
| // Emit any deferred cleanups. |
| { |
| mlir::OpBuilder::InsertionGuard guard(cgf.builder); |
| mlir::Block &cleanupBlock = scope.getCleanupRegion().front(); |
| cgf.builder.setInsertionPointToEnd(&cleanupBlock); |
| |
| if (hasDeferredCleanups) { |
| for (const PendingCleanupEntry &entry : llvm::reverse(llvm::make_range( |
| cgf.deferredConditionalCleanupStack.begin() + oldSize, |
| cgf.deferredConditionalCleanupStack.end()))) { |
| if (entry.activeFlag.isValid()) { |
| // We may have hoisted this alloca out of the cleanup scope. If so, |
| // we will have also hoisted any casts between it and the address that |
| // we stored in the deferredConditionalCleanupStack. While I can't |
| // find a case where this actually happens, there is a theoretical |
| // possibility that we could have a second address that uses an |
| // alloca that has already been hoisted but a different cast chain. |
| // This assert guards against that possibility. |
| assert(entry.addr.getUnderlyingAllocaOp() && |
| (entry.addr.getUnderlyingAllocaOp()->getBlock() == |
| entry.addr.getPointer().getDefiningOp()->getBlock()) && |
| "alloca and cast are in different blocks"); |
| mlir::Value flag = |
| cgf.builder.createLoad(scope.getLoc(), entry.activeFlag); |
| cir::IfOp::create( |
| cgf.builder, scope.getLoc(), flag, /*withElseRegion=*/false, |
| [&](mlir::OpBuilder &b, mlir::Location loc) { |
| cgf.emitDestroy(entry.addr, entry.type, entry.destroyer); |
| cgf.builder.createYield(loc); |
| }); |
| } else { |
| cgf.emitDestroy(entry.addr, entry.type, entry.destroyer); |
| } |
| } |
| } |
| cgf.builder.createYield(scope.getLoc()); |
| } |
| |
| cgf.deferredConditionalCleanupStack.truncate(oldSize); |
| cgf.builder.setInsertionPointAfter(scope); |
| |
| // Promote any lifetime-extended cleanups onto the EH scope stack. The new |
| // cir.cleanup.scope ops created here will wrap any code in the enclosing |
| // scope, including reloads of any spilled values below, so the |
| // lifetime-extended destructors run at the correct point. |
| cleanups.forceLifetimeExtendedCleanups(); |
| |
| // Reload spilled values now that the builder is after the closed scope. |
| for (auto [addr, valPtr] : llvm::zip(tempAllocas, valuesToReload)) { |
| if (!addr.isValid()) |
| continue; |
| *valPtr = cgf.builder.createLoad(valPtr->getLoc(), addr); |
| } |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // EHScopeStack |
| //===----------------------------------------------------------------------===// |
| |
| void EHScopeStack::Cleanup::anchor() {} |
| |
| EHScopeStack::stable_iterator |
| EHScopeStack::getInnermostActiveNormalCleanup() const { |
| stable_iterator si = getInnermostNormalCleanup(); |
| stable_iterator se = stable_end(); |
| while (si != se) { |
| EHCleanupScope &cleanup = llvm::cast<EHCleanupScope>(*find(si)); |
| if (cleanup.isActive()) |
| return si; |
| si = cleanup.getEnclosingNormalCleanup(); |
| } |
| return stable_end(); |
| } |
| |
| /// Push an entry of the given size onto this protected-scope stack. |
| char *EHScopeStack::allocate(size_t size) { |
| size = llvm::alignTo(size, ScopeStackAlignment); |
| if (!startOfBuffer) { |
| unsigned capacity = llvm::PowerOf2Ceil(std::max<size_t>(size, 1024ul)); |
| startOfBuffer = std::make_unique<char[]>(capacity); |
| startOfData = endOfBuffer = startOfBuffer.get() + capacity; |
| } else if (static_cast<size_t>(startOfData - startOfBuffer.get()) < size) { |
| unsigned currentCapacity = endOfBuffer - startOfBuffer.get(); |
| unsigned usedCapacity = |
| currentCapacity - (startOfData - startOfBuffer.get()); |
| unsigned requiredCapacity = usedCapacity + size; |
| // We know from the 'else if' condition that requiredCapacity is greater |
| // than currentCapacity. |
| unsigned newCapacity = llvm::PowerOf2Ceil(requiredCapacity); |
| |
| std::unique_ptr<char[]> newStartOfBuffer = |
| std::make_unique<char[]>(newCapacity); |
| char *newEndOfBuffer = newStartOfBuffer.get() + newCapacity; |
| char *newStartOfData = newEndOfBuffer - usedCapacity; |
| memcpy(newStartOfData, startOfData, usedCapacity); |
| startOfBuffer.swap(newStartOfBuffer); |
| endOfBuffer = newEndOfBuffer; |
| startOfData = newStartOfData; |
| } |
| |
| assert(startOfBuffer.get() + size <= startOfData); |
| startOfData -= size; |
| return startOfData; |
| } |
| |
| void EHScopeStack::deallocate(size_t size) { |
| startOfData += llvm::alignTo(size, ScopeStackAlignment); |
| } |
| |
| void *EHScopeStack::pushCleanup(CleanupKind kind, size_t size) { |
| char *buffer = allocate(EHCleanupScope::getSizeForCleanupSize(size)); |
| bool isNormalCleanup = kind & NormalCleanup; |
| bool isEHCleanup = kind & EHCleanup; |
| bool isLifetimeMarker = kind & LifetimeMarker; |
| bool skipCleanupScope = false; |
| |
| cir::CleanupKind cleanupKind = cir::CleanupKind::All; |
| if (isEHCleanup && cgf->getLangOpts().Exceptions) { |
| cleanupKind = |
| isNormalCleanup ? cir::CleanupKind::All : cir::CleanupKind::EH; |
| } else { |
| // Exceptions are disabled (or no EH flag was requested). Drop the EH |
| // flag so the scope entry stays consistent with the op's cleanup kind. |
| isEHCleanup = false; |
| if (isNormalCleanup) |
| cleanupKind = cir::CleanupKind::Normal; |
| else |
| skipCleanupScope = true; |
| } |
| |
| cir::CleanupScopeOp cleanupScope = nullptr; |
| if (!skipCleanupScope) { |
| CIRGenBuilderTy &builder = cgf->getBuilder(); |
| mlir::Location loc = builder.getUnknownLoc(); |
| cleanupScope = cir::CleanupScopeOp::create( |
| builder, loc, cleanupKind, |
| /*bodyBuilder=*/ |
| [&](mlir::OpBuilder &b, mlir::Location loc) { |
| // Terminations will be handled in popCleanup |
| }, |
| /*cleanupBuilder=*/ |
| [&](mlir::OpBuilder &b, mlir::Location loc) { |
| // Terminations will be handled after emiting cleanup |
| }); |
| |
| builder.setInsertionPointToEnd(&cleanupScope.getBodyRegion().back()); |
| } |
| |
| // Per C++ [except.terminate], it is implementation-defined whether none, |
| // some, or all cleanups are called before std::terminate. Thus, when |
| // terminate is the current EH scope, we may skip adding any EH cleanup |
| // scopes. |
| if (innermostEHScope != stable_end() && |
| find(innermostEHScope)->getKind() == EHScope::Terminate) |
| isEHCleanup = false; |
| |
| EHCleanupScope *scope = new (buffer) |
| EHCleanupScope(isNormalCleanup, isEHCleanup, size, cleanupScope, |
| innermostNormalCleanup, innermostEHScope); |
| |
| if (isNormalCleanup) |
| innermostNormalCleanup = stable_begin(); |
| |
| if (isEHCleanup) |
| innermostEHScope = stable_begin(); |
| |
| if (isLifetimeMarker) |
| cgf->cgm.errorNYI("push lifetime marker cleanup"); |
| |
| // With Windows -EHa, Invoke llvm.seh.scope.begin() for EHCleanup |
| if (cgf->getLangOpts().EHAsynch && isEHCleanup && !isLifetimeMarker && |
| cgf->getTarget().getCXXABI().isMicrosoft()) |
| cgf->cgm.errorNYI("push seh cleanup"); |
| |
| return scope->getCleanupBuffer(); |
| } |
| |
| void EHScopeStack::popCleanup() { |
| assert(!empty() && "popping exception stack when not empty"); |
| |
| assert(isa<EHCleanupScope>(*begin())); |
| EHCleanupScope &cleanup = cast<EHCleanupScope>(*begin()); |
| innermostNormalCleanup = cleanup.getEnclosingNormalCleanup(); |
| innermostEHScope = cleanup.getEnclosingEHScope(); |
| deallocate(cleanup.getAllocatedSize()); |
| |
| cir::CleanupScopeOp cleanupScope = cleanup.getCleanupScopeOp(); |
| if (cleanupScope) { |
| auto *block = &cleanupScope.getBodyRegion().back(); |
| if (!block->mightHaveTerminator()) { |
| mlir::OpBuilder::InsertionGuard guard(cgf->getBuilder()); |
| cgf->getBuilder().setInsertionPointToEnd(block); |
| cir::YieldOp::create(cgf->getBuilder(), |
| cgf->getBuilder().getUnknownLoc()); |
| } |
| cgf->getBuilder().setInsertionPointAfter(cleanupScope); |
| } |
| |
| // Destroy the cleanup. |
| cleanup.destroy(); |
| } |
| |
| bool EHScopeStack::requiresCatchOrCleanup() const { |
| for (stable_iterator si = getInnermostEHScope(); si != stable_end();) { |
| if (auto *cleanup = dyn_cast<EHCleanupScope>(&*find(si))) { |
| if (cleanup->isLifetimeMarker()) { |
| // Skip lifetime markers and continue from the enclosing EH scope |
| assert(!cir::MissingFeatures::emitLifetimeMarkers()); |
| continue; |
| } |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /// The given cleanup block is being deactivated. Configure a cleanup variable |
| /// if necessary. |
| static void setupCleanupBlockDeactivation(CIRGenFunction &cgf, |
| EHScopeStack::stable_iterator c, |
| mlir::Operation *dominatingIP) { |
| EHCleanupScope &scope = cast<EHCleanupScope>(*cgf.ehStack.find(c)); |
| |
| assert((scope.isNormalCleanup() || scope.isEHCleanup()) && |
| "cleanup block is neither normal nor EH?"); |
| |
| scope.setTestFlagInNormalCleanup(scope.isNormalCleanup()); |
| scope.setTestFlagInEHCleanup(scope.isEHCleanup()); |
| |
| CIRGenBuilderTy &builder = cgf.getBuilder(); |
| |
| // If the cleanup block doesn't exist yet, create it and set its initial |
| // value to `true`. If we are inside a conditional branch, the value must be |
| // initialized before the conditional branch begins. |
| Address var = scope.getActiveFlag(); |
| if (!var.isValid()) { |
| mlir::Location loc = builder.getUnknownLoc(); |
| |
| var = cgf.createTempAllocaWithoutCast(builder.getBoolTy(), CharUnits::One(), |
| loc, "cleanup.isactive"); |
| scope.setActiveFlag(var); |
| |
| assert(dominatingIP && "no existing variable and no dominating IP!"); |
| |
| if (cgf.isInConditionalBranch()) { |
| mlir::Value val = builder.getBool(true, loc); |
| cgf.setBeforeOutermostConditional(val, var); |
| } else { |
| mlir::OpBuilder::InsertionGuard guard(builder); |
| builder.setInsertionPoint(dominatingIP); |
| builder.createFlagStore(loc, true, var.getPointer()); |
| } |
| } |
| |
| // The code above sets the `isActive` flag to `true` as its initial state |
| // at the point where the variable is created. The code below sets it to |
| // `false` at the point where the cleanup is deactivated. |
| mlir::Location loc = builder.getUnknownLoc(); |
| builder.createFlagStore(loc, false, var.getPointer()); |
| } |
| |
| /// Deactive a cleanup that was created in an active state. |
| void CIRGenFunction::deactivateCleanupBlock(EHScopeStack::stable_iterator c, |
| mlir::Operation *dominatingIP) { |
| assert(c != ehStack.stable_end() && "deactivating bottom of stack?"); |
| EHCleanupScope &scope = cast<EHCleanupScope>(*ehStack.find(c)); |
| assert(scope.isActive() && "double deactivation"); |
| |
| // If it's the top of the stack, just pop it, but do so only if it belongs |
| // to the current RunCleanupsScope. |
| if (c == ehStack.stable_begin() && |
| currentCleanupStackDepth.strictlyEncloses(c)) { |
| popCleanupBlock(/*forDeactivation=*/true); |
| return; |
| } |
| |
| // Otherwise, follow the general case. |
| setupCleanupBlockDeactivation(*this, c, dominatingIP); |
| |
| scope.setActive(false); |
| } |
| |
| static void emitCleanup(CIRGenFunction &cgf, cir::CleanupScopeOp cleanupScope, |
| EHScopeStack::Cleanup *cleanup, |
| EHScopeStack::Cleanup::Flags flags, |
| Address activeFlag) { |
| CIRGenBuilderTy &builder = cgf.getBuilder(); |
| mlir::Block &block = cleanupScope.getCleanupRegion().back(); |
| |
| mlir::OpBuilder::InsertionGuard guard(builder); |
| builder.setInsertionPointToStart(&block); |
| |
| // Ask the cleanup to emit itself. |
| assert(cgf.haveInsertPoint() && "expected insertion point"); |
| |
| if (activeFlag.isValid()) { |
| mlir::Location loc = cleanupScope.getLoc(); |
| mlir::Value isActive = builder.createFlagLoad(loc, activeFlag.getPointer()); |
| cir::IfOp::create(builder, loc, isActive, |
| /*withElseRegion=*/false, |
| /*thenBuilder=*/ |
| [&](mlir::OpBuilder &, mlir::Location) { |
| cleanup->emit(cgf, flags); |
| assert(cgf.haveInsertPoint() && |
| "cleanup ended with no insertion point?"); |
| builder.createYield(loc); |
| }); |
| } else { |
| cleanup->emit(cgf, flags); |
| assert(cgf.haveInsertPoint() && "cleanup ended with no insertion point?"); |
| } |
| |
| mlir::Block &cleanupRegionLastBlock = cleanupScope.getCleanupRegion().back(); |
| if (cleanupRegionLastBlock.empty() || |
| !cleanupRegionLastBlock.back().hasTrait<mlir::OpTrait::IsTerminator>()) { |
| mlir::OpBuilder::InsertionGuard guardCase(builder); |
| builder.setInsertionPointToEnd(&cleanupRegionLastBlock); |
| builder.createYield(cleanupScope.getLoc()); |
| } |
| } |
| |
| /// Check whether a cleanup scope body contains any non-yield exits that branch |
| /// through the cleanup. These exits branch through the cleanup and require |
| /// the normal cleanup to be executed even when the cleanup has been |
| /// deactivated. |
| static bool bodyHasBranchThroughExits(mlir::Region &bodyRegion) { |
| return bodyRegion |
| .walk([&](mlir::Operation *op) { |
| if (isa<cir::ReturnOp, cir::GotoOp>(op)) |
| return mlir::WalkResult::interrupt(); |
| return mlir::WalkResult::advance(); |
| }) |
| .wasInterrupted(); |
| } |
| |
| /// Pop a cleanup block from the stack. |
| /// |
| /// \param forDeactivation - When true, this indicates that the cleanup block |
| /// is being popped because it was deactivated while at the top of the stack. |
| void CIRGenFunction::popCleanupBlock(bool forDeactivation) { |
| assert(!ehStack.empty() && "cleanup stack is empty!"); |
| assert(isa<EHCleanupScope>(*ehStack.begin()) && "top not a cleanup!"); |
| EHCleanupScope &scope = cast<EHCleanupScope>(*ehStack.begin()); |
| |
| // If we pushed an EH-only cleanup but exceptions are disabled, it will leave |
| // an effectively empty cleanup on the EH stack. In that case, there is |
| // nothing to do here except pop the cleanup. |
| cir::CleanupScopeOp cleanupScope = scope.getCleanupScopeOp(); |
| if (!cleanupScope) { |
| assert(!scope.isNormalCleanup() && !scope.isEHCleanup() && |
| "missing cir.cleanup.scope for active cleanup"); |
| ehStack.popCleanup(); |
| return; |
| } |
| |
| bool requiresNormalCleanup = scope.isNormalCleanup(); |
| bool requiresEHCleanup = scope.isEHCleanup(); |
| |
| // When we're popping a cleanup to deactivate it, we need to know if anything |
| // in the cleanup scope body region branches through the cleanup handler |
| // before the entire cleanup scope body has executed. If the cleanup scope |
| // body falls through, we don't want to emit normal cleanup code. However, |
| // if the cleanup body region contains early exits (return or goto), we do |
| // need to execute the normal cleanup when the early exit is taken. To handle |
| // that case, we guard the cleanup with an "active" flag so that it executes |
| // conditionally and set the flag to false when the cleanup body falls |
| // through. Classic codegen tracks this state with "hasBranches" and |
| // "getFixupDepth" on the cleanup scope, but because CIR uses structured |
| // control flow, we need to check for early exits and insert the active |
| // flag handling here. Note that when a cleanup is deactivated while not at |
| // the top of the stack, the active flag gets created in |
| // setupCleanupBlockDeactivation. |
| if (forDeactivation && requiresNormalCleanup) { |
| if (bodyHasBranchThroughExits(cleanupScope.getBodyRegion())) { |
| // The active flag shouldn't exist if the scope was at the top of the |
| // stack when it was deactivated. |
| assert(!scope.getActiveFlag().isValid() && "active flag already set"); |
| |
| // Create the flag. |
| mlir::Location loc = builder.getUnknownLoc(); |
| Address activeFlag = createTempAllocaWithoutCast( |
| builder.getBoolTy(), CharUnits::One(), loc, "cleanup.isactive"); |
| |
| // Initialize the flag to true before the cleanup scope (the point where |
| // the cleanup becomes active). |
| { |
| mlir::OpBuilder::InsertionGuard guard(builder); |
| builder.setInsertionPoint(cleanupScope); |
| builder.createFlagStore(loc, true, activeFlag.getPointer()); |
| } |
| |
| // Set the flag to false at the end of the cleanup scope body region. |
| assert(builder.getInsertionBlock() == |
| &cleanupScope.getBodyRegion().back() && |
| "expected insertion point in cleanup body"); |
| builder.createFlagStore(loc, false, activeFlag.getPointer()); |
| |
| scope.setActiveFlag(activeFlag); |
| scope.setTestFlagInNormalCleanup(true); |
| } else { |
| // If the cleanup was pushed on the stack as normal+eh, downgrade it to |
| // eh-only. |
| if (requiresEHCleanup) |
| cleanupScope.setCleanupKind(cir::CleanupKind::EH); |
| requiresNormalCleanup = false; |
| } |
| } |
| |
| Address normalActiveFlag = scope.shouldTestFlagInNormalCleanup() |
| ? scope.getActiveFlag() |
| : Address::invalid(); |
| Address ehActiveFlag = scope.shouldTestFlagInEHCleanup() |
| ? scope.getActiveFlag() |
| : Address::invalid(); |
| |
| // If we don't need the cleanup at all, we're done. |
| if (!requiresNormalCleanup && !requiresEHCleanup) { |
| // If we get here, the cleanup scope isn't needed. Rather than try to move |
| // the contents of its body region out of the cleanup and erase it, we just |
| // add a yield to the cleanup region to make it valid but no-op. It will be |
| // erased during canonicalization. |
| mlir::Block &cleanupBlock = cleanupScope.getCleanupRegion().back(); |
| if (!cleanupBlock.mightHaveTerminator()) { |
| mlir::OpBuilder::InsertionGuard guard(builder); |
| builder.setInsertionPointToEnd(&cleanupBlock); |
| cir::YieldOp::create(builder, builder.getUnknownLoc()); |
| } |
| ehStack.popCleanup(); |
| return; |
| } |
| |
| // Copy the cleanup emission data out. This uses either a stack |
| // array or malloc'd memory, depending on the size, which is |
| // behavior that SmallVector would provide, if we could use it |
| // here. Unfortunately, if you ask for a SmallVector<char>, the |
| // alignment isn't sufficient. |
| auto *cleanupSource = reinterpret_cast<char *>(scope.getCleanupBuffer()); |
| alignas(EHScopeStack::ScopeStackAlignment) char |
| cleanupBufferStack[8 * sizeof(void *)]; |
| std::unique_ptr<char[]> cleanupBufferHeap; |
| size_t cleanupSize = scope.getCleanupSize(); |
| EHScopeStack::Cleanup *cleanup; |
| |
| // This is necessary because we are going to deallocate the cleanup |
| // (in popCleanup) before we emit it. |
| if (cleanupSize <= sizeof(cleanupBufferStack)) { |
| memcpy(cleanupBufferStack, cleanupSource, cleanupSize); |
| cleanup = reinterpret_cast<EHScopeStack::Cleanup *>(cleanupBufferStack); |
| } else { |
| cleanupBufferHeap.reset(new char[cleanupSize]); |
| memcpy(cleanupBufferHeap.get(), cleanupSource, cleanupSize); |
| cleanup = |
| reinterpret_cast<EHScopeStack::Cleanup *>(cleanupBufferHeap.get()); |
| } |
| |
| EHScopeStack::Cleanup::Flags cleanupFlags; |
| if (scope.isNormalCleanup()) |
| cleanupFlags.setIsNormalCleanupKind(); |
| if (scope.isEHCleanup()) |
| cleanupFlags.setIsEHCleanupKind(); |
| |
| // Determine the active flag for the cleanup handler. |
| Address cleanupActiveFlag = normalActiveFlag.isValid() ? normalActiveFlag |
| : ehActiveFlag.isValid() ? ehActiveFlag |
| : Address::invalid(); |
| |
| // In CIR, the cleanup code is emitted into the cleanup region of the |
| // cir.cleanup.scope op. There is no CFG threading needed — the FlattenCFG |
| // pass handles lowering the structured cleanup scope. |
| ehStack.popCleanup(); |
| scope.markEmitted(); |
| emitCleanup(*this, cleanupScope, cleanup, cleanupFlags, cleanupActiveFlag); |
| } |
| |
| /// Pops cleanup blocks until the given savepoint is reached. |
| void CIRGenFunction::popCleanupBlocks( |
| EHScopeStack::stable_iterator oldCleanupStackDepth, |
| ArrayRef<mlir::Value *> valuesToReload) { |
| // If the current stack depth is the same as the cleanup stack depth, |
| // we won't be exiting any cleanup scopes, so we don't need to reload |
| // any values. |
| bool requiresCleanup = false; |
| for (auto it = ehStack.begin(), ie = ehStack.find(oldCleanupStackDepth); |
| it != ie; ++it) { |
| if (isa<EHCleanupScope>(&*it)) { |
| requiresCleanup = true; |
| break; |
| } |
| } |
| |
| // If there are values that we need to keep live, spill them now before |
| // we pop the cleanup blocks. These are passed as pointers to mlir::Value |
| // because we're going to replace them with the reloaded value. |
| SmallVector<Address> tempAllocas; |
| if (requiresCleanup) { |
| for (mlir::Value *valPtr : valuesToReload) { |
| mlir::Value val = *valPtr; |
| if (!val) |
| continue; |
| |
| // TODO(cir): Check for static allocas. |
| |
| Address temp = createDefaultAlignTempAlloca(val.getType(), val.getLoc(), |
| "tmp.exprcleanup"); |
| tempAllocas.push_back(temp); |
| builder.createStore(val.getLoc(), val, temp); |
| } |
| } |
| |
| // Pop cleanup blocks until we reach the base stack depth for the |
| // current scope. |
| while (ehStack.stable_begin() != oldCleanupStackDepth) |
| popCleanupBlock(); |
| |
| // Reload the values that we spilled, if necessary. |
| if (requiresCleanup) { |
| for (auto [addr, valPtr] : llvm::zip(tempAllocas, valuesToReload)) { |
| mlir::Location loc = valPtr->getLoc(); |
| *valPtr = builder.createLoad(loc, addr); |
| } |
| } |
| } |
| |
| /// Pops cleanup blocks until the given savepoint is reached, then add the |
| /// cleanups from the given savepoint in the lifetime-extended cleanups stack. |
| void CIRGenFunction::popCleanupBlocks( |
| EHScopeStack::stable_iterator oldCleanupStackDepth, |
| size_t oldLifetimeExtendedSize, ArrayRef<mlir::Value *> valuesToReload) { |
| popCleanupBlocks(oldCleanupStackDepth, valuesToReload); |
| |
| // Promote deferred lifetime-extended cleanups onto the EH scope stack. |
| for (const PendingCleanupEntry &cleanup : llvm::make_range( |
| lifetimeExtendedCleanupStack.begin() + oldLifetimeExtendedSize, |
| lifetimeExtendedCleanupStack.end())) |
| pushPendingCleanupToEHStack(cleanup); |
| lifetimeExtendedCleanupStack.truncate(oldLifetimeExtendedSize); |
| } |