24#include <mlir/Dialect/Arith/IR/Arith.h>
25#include <mlir/IR/BuiltinOps.h>
26#include <mlir/IR/Dominance.h>
27#include <mlir/IR/OperationSupport.h>
29#include <llvm/ADT/DenseMap.h>
30#include <llvm/ADT/DenseSet.h>
31#include <llvm/ADT/Hashing.h>
32#include <llvm/ADT/PostOrderIterator.h>
33#include <llvm/ADT/SmallVector.h>
39#define GEN_PASS_DEF_REDUNDANTOPERATIONELIMINATIONPASS
50#define DEBUG_TYPE "llzk-duplicate-op-elim"
57static bool isDuplicateEliminationCandidate(Operation *op) {
58 if (isa<NonDetOp>(op) || op->hasTrait<OpTrait::IsTerminator>() || op->getNumRegions() != 0 ||
59 op->getNumSuccessors() != 0) {
63 return isa<ConstraintOpInterface>(op) || isMemoryEffectFree(op);
69class OperationComparator {
71 explicit OperationComparator(Operation *o) : op(o), operands(o->getOperands()) {}
73 OperationComparator(Operation *o,
const TranslationMap &m) : op(o) {
74 for (Value operand : op->getOperands()) {
75 if (
auto it = m.find(operand); it != m.end()) {
76 operands.push_back(it->second);
78 operands.push_back(operand);
83 Operation *getOp()
const {
return op; }
85 const SmallVector<Value> &getOperands()
const {
return operands; }
87 bool isCommutative()
const {
return op->hasTrait<OpTrait::IsCommutative>(); }
89 friend bool operator==(
const OperationComparator &lhs,
const OperationComparator &rhs) {
90 if (!OperationEquivalence::isEquivalentTo(
91 lhs.op, rhs.op, OperationEquivalence::ignoreValueEquivalence,
92 nullptr, OperationEquivalence::IgnoreLocations
99 if (lhs.isCommutative() && lhs.operands.size() == 2) {
100 return (lhs.operands[0] == rhs.operands[0] && lhs.operands[1] == rhs.operands[1]) ||
101 (lhs.operands[0] == rhs.operands[1] && lhs.operands[1] == rhs.operands[0]);
104 return lhs.operands == rhs.operands;
109 SmallVector<Value> operands;
116template <>
struct DenseMapInfo<OperationComparator> {
118 hash_code opHash = mlir::OperationEquivalence::computeHash(
119 oc.getOp(), mlir::OperationEquivalence::ignoreHashValue,
120 mlir::OperationEquivalence::ignoreHashValue, mlir::OperationEquivalence::IgnoreLocations
123 ArrayRef<Value> operands = oc.getOperands();
124 hash_code operandHash;
125 if (oc.isCommutative() && operands.size() == 2) {
126 size_t lhsHash = hash_value(operands[0]);
127 size_t rhsHash = hash_value(operands[1]);
128 if (rhsHash < lhsHash) {
129 std::swap(lhsHash, rhsHash);
131 operandHash = hash_combine(lhsHash, rhsHash);
133 operandHash = hash_combine_range(operands.begin(), operands.end());
136 return hash_combine(opHash, operandHash);
138 static bool isEqual(
const OperationComparator &lhs,
const OperationComparator &rhs) {
148 using Base = RedundantOperationEliminationPassBase<PassImpl>;
151 void runOnOperation()
override {
152 SymbolTableCollection symbolTables;
156 auto &cga = getAnalysis<CallGraphAnalysis>();
157 const llzk::CallGraph *callGraph = &cga.getCallGraph();
158 for (
const llzk::CallGraphNode *node : llvm::post_order(callGraph)) {
159 if (!node->isExternal()) {
160 runOnFunc(symbolTables, node->getCalledFunction());
169 bool isPurposelessConstrainFunc(
170 SymbolTableCollection &symbolTables, FuncDefOp fn, DenseSet<Operation *> &activeFunctions
182 if (!activeFunctions.insert(fn.getOperation()).second) {
189 fn.walk([&](Operation *op) {
190 if (op == fn.getOperation()) {
191 return WalkResult::advance();
193 if (isa<EmitEqualityOp, EmitContainmentOp, AssertOp>(op)) {
195 return WalkResult::interrupt();
196 }
else if (
auto callOp = dyn_cast<CallOp>(op)) {
197 if (!callsPurposelessConstrainFunc(symbolTables, callOp, activeFunctions)) {
199 return WalkResult::interrupt();
201 return WalkResult::advance();
202 }
else if (isMemoryEffectFree(op)) {
203 return WalkResult::advance();
210 return WalkResult::interrupt();
212 return WalkResult::advance();
214 activeFunctions.erase(fn.getOperation());
219 bool callsPurposelessConstrainFunc(
220 SymbolTableCollection &symbolTables, CallOp call, DenseSet<Operation *> &activeFunctions
223 return succeeded(callLookup) &&
224 isPurposelessConstrainFunc(symbolTables, callLookup->get(), activeFunctions);
227 void runOnFunc(SymbolTableCollection &symbolTables, CallableOpInterface callable) {
229 SmallVector<Operation *> redundantOps;
230 DenseSet<OperationComparator> uniqueOps;
231 DominanceInfo domInfo(callable);
233 auto unnecessaryOpCheck = [&](Operation *op) ->
bool {
234 if (
auto emiteq = dyn_cast<EmitEqualityOp>(op);
235 emiteq && emiteq.getLhs() == emiteq.getRhs()) {
236 redundantOps.push_back(op);
240 if (
auto callOp = dyn_cast<CallOp>(op)) {
241 DenseSet<Operation *> activeFunctions;
242 if (callsPurposelessConstrainFunc(symbolTables, callOp, activeFunctions)) {
243 redundantOps.push_back(op);
250 callable.walk([&](Operation *op) {
251 if (op == callable.getOperation()) {
252 return WalkResult::advance();
256 if (unnecessaryOpCheck(op)) {
257 return WalkResult::advance();
262 if (isDuplicateEliminationCandidate(op)) {
263 OperationComparator comp(op, map);
264 if (
auto it = uniqueOps.find(comp);
265 it != uniqueOps.end() && domInfo.dominates(it->getOp(), op)) {
266 redundantOps.push_back(op);
267 for (
unsigned opNum = 0; opNum < op->getNumResults(); opNum++) {
268 map[op->getResult(opNum)] = it->getOp()->getResult(opNum);
271 uniqueOps.insert(comp);
274 return WalkResult::advance();
277 DenseSet<Operation *> redundantOpSet;
278 for (Operation *op : redundantOps) {
279 redundantOpSet.insert(op);
282 SmallVector<Operation *> deadOpCandidates;
283 DenseSet<Operation *> queuedDeadOps;
284 auto enqueueDeadOpCandidate = [&](Value value) {
285 Operation *definingOp = value.getDefiningOp();
286 if (!definingOp || redundantOpSet.count(definingOp) ||
287 !queuedDeadOps.insert(definingOp).second) {
290 deadOpCandidates.push_back(definingOp);
293 for (Operation *op : redundantOps) {
294 LLVM_DEBUG(llvm::dbgs() <<
"Removing op: " << *op <<
'\n');
295 for (Value result : op->getResults()) {
296 if (!result.use_empty()) {
297 auto it = map.find(result);
299 it != map.end(),
"failed to find a replacement value for redundant operation result"
301 LLVM_DEBUG(llvm::dbgs() <<
"Replacing " << it->first <<
" with " << it->second <<
'\n');
302 result.replaceAllUsesWith(it->second);
306 SmallVector<Value> operands(op->getOperands());
308 for (Value operand : operands) {
309 enqueueDeadOpCandidate(operand);
315 while (!deadOpCandidates.empty()) {
316 Operation *op = deadOpCandidates.pop_back_val();
317 queuedDeadOps.erase(op);
318 if (!isOpTriviallyDead(op)) {
322 SmallVector<Value> operands(op->getOperands());
323 LLVM_DEBUG(llvm::dbgs() <<
"Removing dead producer: " << *op <<
'\n');
325 for (Value operand : operands) {
326 enqueueDeadOpCandidate(operand);
bool hasAllowWitnessAttr()
Return true iff the function def has the allow_witness attribute.
bool isStructConstrain()
Return true iff the function is within a StructDefOp and named FUNC_NAME_CONSTRAIN.
void ensure(bool condition, const llvm::Twine &errMsg)
std::unordered_map< SourceRef, SourceRefLatticeValue, SourceRef::Hash > TranslationMap
mlir::FailureOr< SymbolLookupResult< T > > resolveCallable(mlir::SymbolTableCollection &symbolTable, mlir::CallOpInterface call)
Based on mlir::CallOpInterface::resolveCallable, but using LLZK lookup helpers.
bool hasUnknownOrNonReadEffect(mlir::Operation *op)
Returns true when op may have an unknown effect or any effect other than memory read.
static unsigned getHashValue(const OperationComparator &oc)
static bool isEqual(const OperationComparator &lhs, const OperationComparator &rhs)