clang 24.0.0git
CIRTransformUtils.cpp
Go to the documentation of this file.
1//===- CIRTransformUtils.cpp - Shared helpers for CIR transforms ----------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10
12
13#include "llvm/ADT/DepthFirstIterator.h"
14
15void cir::collectUnreachable(mlir::Operation *parent,
17 // For every region under `parent`, find the blocks unreachable from the
18 // entry via a forward CFG traversal and collect their ops.
19 llvm::df_iterator_default_set<mlir::Block *, 16> reachable;
20 parent->walk([&](mlir::Region *region) {
21 // Empty regions have no blocks; single-block regions have only the
22 // entry, which is trivially reachable. Either way, nothing to collect.
23 if (region->empty() || region->hasOneBlock())
24 return;
25
26 // We clear this for each region as we walk the parent because each block
27 // is only in one region, so the reachable blocks from previously visited
28 // regions aren't needed.
29 reachable.clear();
30
31 // The depth_first_ext range iterator internally adds each block to the
32 // reachable set as it visits it, so while this loop looks like it doesn't
33 // do anything, it's actually populating the set of reachable blocks in
34 // this region.
35 for (mlir::Block *blk : llvm::depth_first_ext(&region->front(), reachable))
36 (void)blk;
37
38 // Collect the unreachable blocks.
39 for (mlir::Block &blk : *region) {
40 if (reachable.contains(&blk))
41 continue;
42 for (mlir::Operation &op : blk)
43 ops.push_back(&op);
44 }
45 });
46}
47
48mlir::Block *cir::replaceCallWithTryCall(cir::CallOp callOp,
49 mlir::Block *unwindDest,
50 mlir::Location loc,
51 mlir::RewriterBase &rewriter) {
52 mlir::Block *callBlock = callOp->getBlock();
53
54 assert(!callOp.getNothrow() && "call is not expected to throw");
55
56 // Split the block after the call - remaining ops become the normal
57 // destination.
58 mlir::Block *normalDest =
59 rewriter.splitBlock(callBlock, std::next(callOp->getIterator()));
60
61 // Build the try_call to replace the original call.
62 rewriter.setInsertionPoint(callOp);
63 cir::TryCallOp tryCallOp;
64 if (callOp.isIndirect()) {
65 mlir::Value indTarget = callOp.getIndirectCall();
66 auto ptrTy = mlir::cast<cir::PointerType>(indTarget.getType());
67 auto resTy = mlir::cast<cir::FuncType>(ptrTy.getPointee());
68 tryCallOp =
69 cir::TryCallOp::create(rewriter, loc, indTarget, resTy, normalDest,
70 unwindDest, callOp.getArgOperands());
71 } else {
72 mlir::Type resType = callOp->getNumResults() > 0
73 ? callOp->getResult(0).getType()
74 : mlir::Type();
75 tryCallOp =
76 cir::TryCallOp::create(rewriter, loc, callOp.getCalleeAttr(), resType,
77 normalDest, unwindDest, callOp.getArgOperands());
78 }
79
80 // Preserve the call semantics shared by CallOp and TryCallOp. The callee and
81 // operand segments are already populated by TryCallOp::create, and a
82 // throwing call cannot carry the nothrow property. nounwind describes the
83 // callee, so it survives even though this site gains an unwind edge.
84 callOp->getName().walkInherentAttrs(
85 callOp, [&](llvm::StringRef name, mlir::Attribute &attr) {
86 if (name != cir::CIRDialect::getCalleeAttrName() &&
87 name != cir::CIRDialect::getNoThrowAttrName() &&
88 name != cir::CIRDialect::getOperandSegmentSizesAttrName())
89 tryCallOp->setInherentAttr(
90 mlir::StringAttr::get(callOp->getContext(), name), attr);
91 });
92 for (mlir::NamedAttribute attr : callOp->getDiscardableAttrs())
93 tryCallOp->setDiscardableAttr(attr.getName(), attr.getValue());
94
95 // Replace uses of the call result with the try_call result. Use the
96 // rewriter API so any listener (e.g. the pattern rewriter in
97 // FlattenCFG) is notified of the in-place modifications to each user.
98 if (callOp->getNumResults() > 0)
99 rewriter.replaceAllUsesWith(callOp->getResult(0), tryCallOp.getResult());
100
101 rewriter.eraseOp(callOp);
102 return normalDest;
103}
104
105mlir::Block *cir::replaceThrowWithTryThrow(cir::ThrowOp throwOp,
106 mlir::Block *unwindDest,
107 mlir::Location loc,
108 mlir::RewriterBase &rewriter) {
109 // The throw never returns, so the try_throw's normal destination is
110 // literally unreachable. Place it at the end of the parent function
111 // rather than splitting it out of the throw's block in the middle of
112 // the normal control flow.
113 auto funcOp = throwOp->getParentOfType<cir::FuncOp>();
114 assert(funcOp && "throw must be inside a function");
115 mlir::Region &body = funcOp.getBody();
116
117 mlir::Block *normalDest;
118 {
119 mlir::OpBuilder::InsertionGuard guard(rewriter);
120 normalDest = rewriter.createBlock(&body, body.end());
121 cir::UnreachableOp::create(rewriter, loc);
122 }
123
124 // Build the try_throw to replace the original throw.
125 rewriter.setInsertionPoint(throwOp);
126 auto tryThrowOp = cir::TryThrowOp::create(
127 rewriter, loc, throwOp.getExceptionPtr(), throwOp.getTypeInfoAttr(),
128 throwOp.getDtorAttr(), normalDest, unwindDest);
129
130 // The shared inherent state is already set by TryThrowOp::create. Preserve
131 // only auxiliary metadata here.
132 for (mlir::NamedAttribute attr : throwOp->getDiscardableAttrs())
133 tryThrowOp->setDiscardableAttr(attr.getName(), attr.getValue());
134
135 // Erase the throw along with any operations that followed it in its
136 // parent block (typically a cir.unreachable left over from CIR codegen).
137 // They must be removed because try_throw is a terminator and a block
138 // can have only one terminator.
139 mlir::Block *throwBlock = throwOp->getBlock();
140 while (&throwBlock->back() != tryThrowOp)
141 rewriter.eraseOp(&throwBlock->back());
142
143 return normalDest;
144}
void collectUnreachable(mlir::Operation *parent, llvm::SmallVectorImpl< mlir::Operation * > &ops)
Collect ops in blocks that are unreachable from their region's entry, appending them to ops.
mlir::Block * replaceThrowWithTryThrow(cir::ThrowOp throwOp, mlir::Block *unwindDest, mlir::Location loc, mlir::RewriterBase &rewriter)
Replace a cir::ThrowOp with a cir::TryThrowOp whose unwind destination is unwindDest.
mlir::Block * replaceCallWithTryCall(cir::CallOp callOp, mlir::Block *unwindDest, mlir::Location loc, mlir::RewriterBase &rewriter)
Replace a cir::CallOp with a cir::TryCallOp whose unwind destination is unwindDest.