CIRCT 23.0.0git
Loading...
Searching...
No Matches
InlineCalls.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
12#include "mlir/Dialect/Func/IR/FuncOps.h"
13#include "mlir/Dialect/UB/IR/UBOps.h"
14#include "mlir/IR/SymbolTable.h"
15#include "mlir/IR/Threading.h"
16#include "mlir/IR/Visitors.h"
17#include "mlir/Interfaces/CallInterfaces.h"
18#include "mlir/Pass/Pass.h"
19#include "mlir/Transforms/Inliner.h"
20#include "mlir/Transforms/InliningUtils.h"
21#include "llvm/Support/Debug.h"
22
23#define DEBUG_TYPE "llhd-inline-calls"
24
25namespace circt {
26namespace llhd {
27#define GEN_PASS_DEF_INLINECALLSPASS
28#include "circt/Dialect/LLHD/LLHDPasses.h.inc"
29} // namespace llhd
30} // namespace circt
31
32using namespace mlir;
33using namespace circt;
34using namespace llhd;
36
37namespace {
38/// Implementation of the `InlinerInterface` that allows calls in SSACFG regions
39/// nested within `llhd.process`, `llhd.final`, and `llhd.combinational` ops to
40/// be inlined.
41struct FunctionInliner : public InlinerInterface {
42 using InlinerInterface::InlinerInterface;
43
44 bool isLegalToInline(Operation *call, Operation *callable,
45 bool wouldBeCloned) const override {
46 // Only inline `func.func` ops.
47 if (!isa<func::FuncOp>(callable))
48 return false;
49
50 // Only inline into SSACFG regions embedded within LLHD processes.
51 if (!mayHaveSSADominance(*call->getParentRegion()))
52 return false;
53 return call->getParentWithTrait<ProceduralRegion>();
54 }
55
56 bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned,
57 IRMapping &valueMapping) const override {
58 return true;
59 }
60
61 bool isLegalToInline(Operation *op, Region *dest, bool wouldBeCloned,
62 IRMapping &valueMapping) const override {
63 return true;
64 }
65
66 bool shouldAnalyzeRecursively(Operation *op) const override { return false; }
67
68 /// The UB dialect does not implement this inliner hook for its
69 /// `ub.unreachable` terminator, which causes the default implementation to
70 /// abort. Handle the op here instead: like a branch op, it needs no rewrite
71 /// when inlined, since it never transfers control to the continuation block.
72 void handleTerminator(Operation *op, Block *newDest) const override {
73 if (isa<mlir::ub::UnreachableOp>(op))
74 return;
75 InlinerInterface::handleTerminator(op, newDest);
76 }
77};
78
79/// Pass implementation.
80struct InlineCallsPass
81 : public llhd::impl::InlineCallsPassBase<InlineCallsPass> {
82 using CallStack = SmallSetVector<func::FuncOp, 8>;
83 void runOnOperation() override;
84 LogicalResult runOnRegion(Region &region, const SymbolTable &symbolTable,
85 CallStack &callStack);
86};
87} // namespace
88
89void InlineCallsPass::runOnOperation() {
90 auto &symbolTable = getAnalysis<SymbolTable>();
91 if (failed(failableParallelForEach(
92 &getContext(), getOperation().getOps<hw::HWModuleOp>(),
93 [&](auto module) {
94 CallStack callStack;
95 return runOnRegion(module.getBody(), symbolTable, callStack);
96 })))
97 signalPassFailure();
98}
99
100LogicalResult InlineCallsPass::runOnRegion(Region &region,
101 const SymbolTable &symbolTable,
102 CallStack &callStack) {
103 FunctionInliner inliner(&getContext());
104 InlinerConfig config;
105 SmallVector<Operation *> callsToErase;
106 SmallVector<std::pair<Operation *, func::FuncOp>> inlineEndMarkers;
107
108 // Walk all calls in the HW module and inline each. Emit a diagnostic if a
109 // call does not target a `func.func` op or the inliner fails for some reason.
110 // We use a custom version of `Operation::walk` here to ensure that we visit
111 // the inlined operations immediately after visiting the call.
112 for (auto &block : region) {
113 for (auto &op : block) {
114 // Pop all calls that are followed by this op off the call stack.
115 while (!inlineEndMarkers.empty() &&
116 inlineEndMarkers.back().first == &op) {
117 assert(inlineEndMarkers.back().second == callStack.back());
118 LLVM_DEBUG(llvm::dbgs()
119 << "- Finished @"
120 << inlineEndMarkers.back().second.getSymName() << "\n");
121 inlineEndMarkers.pop_back();
122 callStack.pop_back();
123 }
124
125 // Handle nested regions.
126 for (auto &nestedRegion : op.getRegions())
127 if (failed(runOnRegion(nestedRegion, symbolTable, callStack)))
128 return failure();
129
130 // We only care about calls.
131 auto callOp = dyn_cast<func::CallOp>(op);
132 if (!callOp)
133 continue;
134
135 // Make sure we're calling a `func.func`.
136 auto symbol = callOp.getCalleeAttr();
137 auto calledOp = symbolTable.lookup(symbol.getAttr());
138 auto funcOp = dyn_cast<func::FuncOp>(calledOp);
139 if (!funcOp) {
140 auto d = callOp.emitError("function call cannot be inlined: call "
141 "target is not a regular function");
142 d.attachNote(calledOp->getLoc()) << "call target defined here";
143 return failure();
144 }
145
146 // Skip extern declarations (e.g. DPI-C imports) — nothing to inline.
147 if (funcOp.isDeclaration())
148 continue;
149
150 // Ensure that we are not recursively inlining a function, which would
151 // just expand infinitely in the IR.
152 if (!callStack.insert(funcOp))
153 return callOp.emitError("recursive function call cannot be inlined");
154 inlineEndMarkers.push_back({op.getNextNode(), funcOp});
155
156 // Inline the function body and remember the call for later removal. The
157 // `inlineCall` function will inline the function body *after* the call
158 // op, which allows the loop to immediately visit the inlined ops and
159 // handling nested calls.
160 LLVM_DEBUG(llvm::dbgs() << "- Inlining " << callOp << "\n");
161 if (failed(inlineCall(inliner, config.getCloneCallback(), callOp, funcOp,
162 funcOp.getCallableRegion())))
163 return callOp.emitError("function call cannot be inlined");
164 callsToErase.push_back(callOp);
165 ++numInlined;
166 }
167 }
168
169 // Erase all call ops that were successfully inlined.
170 for (auto *callOp : callsToErase)
171 callOp->erase();
172
173 return success();
174}
assert(baseType &&"element must be base type")
Signals that an operation's regions are procedural.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.