LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
LLZKInlineFreeFunctionsPass.cpp
Go to the documentation of this file.
1//===-- LLZKInlineFreeFunctionsPass.cpp -------------------------*- C++ -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2026 Project LLZK
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
13//===----------------------------------------------------------------------===//
14
19
20#include <mlir/IR/BuiltinOps.h>
21#include <mlir/IR/SymbolTable.h>
22#include <mlir/Transforms/InliningUtils.h>
23
24#include <llvm/ADT/DenseSet.h>
25#include <llvm/ADT/SCCIterator.h>
26#include <llvm/ADT/SmallVector.h>
27#include <llvm/Support/Debug.h>
28
29namespace llzk {
30#define GEN_PASS_DEF_INLINEFREEFUNCTIONSPASS
32} // namespace llzk
33
34#define DEBUG_TYPE "llzk-inline-free-functions"
35
36using namespace mlir;
37using namespace llzk;
38using namespace llzk::function;
39
40namespace {
41
45static bool isInlinableFreeFunction(FuncDefOp func, ModuleOp root) {
46 return func->getParentOp() == root;
47}
48
52static bool isInlinableCallSite(CallOp call, ModuleOp root) {
53 return call->getParentOfType<FuncDefOp>() && call->getParentOfType<ModuleOp>() == root;
54}
55
60static llvm::DenseSet<Operation *> collectRecursiveFunctions(const llzk::CallGraph &cg) {
61 llvm::DenseSet<Operation *> recursive;
62 for (auto scc = llvm::scc_begin(&cg); !scc.isAtEnd(); ++scc) {
63 if (scc->size() == 1 && !scc.hasCycle()) {
64 continue;
65 }
66 for (const llzk::CallGraphNode *node : *scc) {
67 if (!node->isExternal()) {
68 recursive.insert(node->getCalledFunction().getOperation());
69 }
70 }
71 }
72 return recursive;
73}
74
77static FuncDefOp resolveFreeCallee(
78 CallOp call, ModuleOp root, SymbolTableCollection &tables,
79 const llvm::DenseSet<Operation *> &skippedCallees
80) {
81 auto tgtRes = call.getCalleeTarget(tables);
82 if (failed(tgtRes)) {
83 return nullptr;
84 }
85 FuncDefOp callee = tgtRes->get();
86 if (!isInlinableFreeFunction(callee, root) || callee.isExternal() ||
87 skippedCallees.contains(callee)) {
88 return nullptr;
89 }
90 return callee;
91}
92
94struct FreeFunctionCall {
95 CallOp call;
96 FuncDefOp callee;
97};
98
101static SmallVector<FreeFunctionCall> collectFreeFunctionCalls(
102 ModuleOp mod, SymbolTableCollection &tables, const llvm::DenseSet<Operation *> &skippedCallees
103) {
104 SmallVector<FreeFunctionCall> calls;
105 mod.walk([&](CallOp call) {
106 if (!isInlinableCallSite(call, mod)) {
107 return;
108 }
109 if (FuncDefOp callee = resolveFreeCallee(call, mod, tables, skippedCallees)) {
110 calls.push_back({call, callee});
111 }
112 });
113 return calls;
114}
115
119static SmallVector<FuncDefOp> collectUnusedHelpers(ModuleOp mod) {
120 SymbolUseGraph useGraph(mod.getOperation());
121 SmallVector<FuncDefOp> unusedFunctions;
122 for (FuncDefOp func : mod.getOps<FuncDefOp>()) {
123 if (func.isExternal()) {
124 continue;
125 }
126 const SymbolUseGraphNode *node = useGraph.lookupNode(func);
127 if (!node || !node->hasPredecessor()) {
128 unusedFunctions.push_back(func);
129 }
130 }
131 return unusedFunctions;
132}
133
134class PassImpl : public llzk::impl::InlineFreeFunctionsPassBase<PassImpl> {
135 using Base = InlineFreeFunctionsPassBase<PassImpl>;
136 using Base::Base;
137
138 void runOnOperation() override {
139 ModuleOp mod = getOperation();
140 SymbolTableCollection tables;
141 InlinerInterface inliner(&getContext());
142 // Seeded with recursive functions; grows with callees that cannot be
143 // inlined.
144 llvm::DenseSet<Operation *> skippedCallees =
145 collectRecursiveFunctions(getAnalysis<CallGraphAnalysis>().getCallGraph());
146
147 inlineCalls(mod, tables, inliner, skippedCallees);
148 removeUnusedFunctions(mod);
149 }
150
157 void inlineCalls(
158 ModuleOp mod, SymbolTableCollection &tables, InlinerInterface &inliner,
159 llvm::DenseSet<Operation *> &skippedCallees
160 ) {
161 SmallVector<FreeFunctionCall> callsToInline =
162 collectFreeFunctionCalls(mod, tables, skippedCallees);
163 while (!callsToInline.empty()) {
164 LLVM_DEBUG({
165 llvm::dbgs() << "[" DEBUG_TYPE "] round found " << callsToInline.size()
166 << " free-function call site(s) to inline\n";
167 });
168
169 for (auto [call, callee] : callsToInline) {
170 if (skippedCallees.contains(callee)) {
171 continue;
172 }
173 if (failed(inlineCall(inliner, call, callee, callee.getCallableRegion(), true))) {
174 call.emitWarning("failed to inline free function call; skipping this callee");
175 skippedCallees.insert(callee);
176 continue;
177 }
178 call.erase();
179 }
180
181 callsToInline = collectFreeFunctionCalls(mod, tables, skippedCallees);
182 }
183 }
184
187 void removeUnusedFunctions(ModuleOp mod) {
188 SmallVector<FuncDefOp> toErase = collectUnusedHelpers(mod);
189 while (!toErase.empty()) {
190 for (FuncDefOp func : toErase) {
191 func.erase();
192 }
193 toErase = collectUnusedHelpers(mod);
194 }
195 }
196};
197} // namespace
#define DEBUG_TYPE
This is a simple port of the mlir::CallGraphNode with llzk::CallGraph as a friend class,...
Definition CallGraph.h:36
This is a port of mlir::CallGraph that has been adapted to use the custom symbol lookup helpers (see ...
Definition CallGraph.h:164
bool hasPredecessor() const
Return true if this node has any predecessors.
Builds a graph structure representing the relationships between symbols and their uses.
const SymbolUseGraphNode * lookupNode(mlir::ModuleOp pathRoot, mlir::SymbolRefAttr path) const
Return the existing node for the symbol reference relative to the given module, else nullptr.
::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::function::FuncDefOp > > getCalleeTarget(::mlir::SymbolTableCollection &tables)
Resolve and return the target FuncDefOp for this CallOp.
Definition Ops.cpp:1203
ExpressionValue mod(const llvm::SMTSolverRef &solver, const ExpressionValue &lhs, const ExpressionValue &rhs)