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"
54static Operation *EMPTY_OP_KEY = llvm::DenseMapInfo<Operation *>::getEmptyKey();
55static Operation *TOMBSTONE_OP_KEY = llvm::DenseMapInfo<Operation *>::getTombstoneKey();
60static bool isDuplicateEliminationCandidate(Operation *op) {
61 if (isa<NonDetOp>(op) || op->hasTrait<OpTrait::IsTerminator>() || op->getNumRegions() != 0 ||
62 op->getNumSuccessors() != 0) {
66 return isa<ConstraintOpInterface>(op) || isMemoryEffectFree(op);
72class OperationComparator {
74 explicit OperationComparator(Operation *o) : op(o) {
75 if (op != EMPTY_OP_KEY && op != TOMBSTONE_OP_KEY) {
76 operands = SmallVector<Value>(op->getOperands());
80 OperationComparator(Operation *o,
const TranslationMap &m) : op(o) {
81 for (Value operand : op->getOperands()) {
82 if (
auto it = m.find(operand); it != m.end()) {
83 operands.push_back(it->second);
85 operands.push_back(operand);
90 Operation *getOp()
const {
return op; }
92 const SmallVector<Value> &getOperands()
const {
return operands; }
94 bool isCommutative()
const {
return op->hasTrait<OpTrait::IsCommutative>(); }
96 friend bool operator==(
const OperationComparator &lhs,
const OperationComparator &rhs) {
97 if (lhs.op == EMPTY_OP_KEY || rhs.op == EMPTY_OP_KEY || lhs.op == TOMBSTONE_OP_KEY ||
98 rhs.op == TOMBSTONE_OP_KEY) {
99 return lhs.op == rhs.op;
102 if (!OperationEquivalence::isEquivalentTo(
103 lhs.op, rhs.op, OperationEquivalence::ignoreValueEquivalence,
104 nullptr, OperationEquivalence::IgnoreLocations
111 if (lhs.isCommutative() && lhs.operands.size() == 2) {
112 return (lhs.operands[0] == rhs.operands[0] && lhs.operands[1] == rhs.operands[1]) ||
113 (lhs.operands[0] == rhs.operands[1] && lhs.operands[1] == rhs.operands[0]);
116 return lhs.operands == rhs.operands;
121 SmallVector<Value> operands;
128template <>
struct DenseMapInfo<OperationComparator> {
129 static OperationComparator
getEmptyKey() {
return OperationComparator(EMPTY_OP_KEY); }
131 return OperationComparator(TOMBSTONE_OP_KEY);
134 if (oc.getOp() == EMPTY_OP_KEY || oc.getOp() == TOMBSTONE_OP_KEY) {
135 return hash_value(oc.getOp());
138 hash_code opHash = mlir::OperationEquivalence::computeHash(
139 oc.getOp(), mlir::OperationEquivalence::ignoreHashValue,
140 mlir::OperationEquivalence::ignoreHashValue, mlir::OperationEquivalence::IgnoreLocations
143 ArrayRef<Value> operands = oc.getOperands();
144 hash_code operandHash;
145 if (oc.isCommutative() && operands.size() == 2) {
146 size_t lhsHash = hash_value(operands[0]);
147 size_t rhsHash = hash_value(operands[1]);
148 if (rhsHash < lhsHash) {
149 std::swap(lhsHash, rhsHash);
151 operandHash = hash_combine(lhsHash, rhsHash);
153 operandHash = hash_combine_range(operands.begin(), operands.end());
156 return hash_combine(opHash, operandHash);
158 static bool isEqual(
const OperationComparator &lhs,
const OperationComparator &rhs) {
168 using Base = RedundantOperationEliminationPassBase<PassImpl>;
171 void runOnOperation()
override {
172 SymbolTableCollection symbolTables;
176 auto &cga = getAnalysis<CallGraphAnalysis>();
177 const llzk::CallGraph *callGraph = &cga.getCallGraph();
178 for (
auto it = llvm::po_begin(callGraph); it != llvm::po_end(callGraph); ++it) {
179 const llzk::CallGraphNode *node = *it;
186 bool isPurposelessConstrainFunc(SymbolTableCollection &symbolTables, FuncDefOp fn) {
199 fn.walk([&](Operation *op) {
200 if (op == fn.getOperation()) {
201 return WalkResult::advance();
203 if (isa<EmitEqualityOp, EmitContainmentOp, AssertOp>(op)) {
205 return WalkResult::interrupt();
206 }
else if (
auto callOp = dyn_cast<CallOp>(op)) {
207 if (!callsPurposelessConstrainFunc(symbolTables, callOp)) {
209 return WalkResult::interrupt();
211 return WalkResult::advance();
212 }
else if (isMemoryEffectFree(op)) {
213 return WalkResult::advance();
220 return WalkResult::interrupt();
222 return WalkResult::advance();
227 bool callsPurposelessConstrainFunc(SymbolTableCollection &symbolTables, CallOp call) {
229 return succeeded(callLookup) && isPurposelessConstrainFunc(symbolTables, callLookup->get());
232 void runOnFunc(SymbolTableCollection &symbolTables, CallableOpInterface callable) {
234 SmallVector<Operation *> redundantOps;
235 DenseSet<OperationComparator> uniqueOps;
236 DominanceInfo domInfo(callable);
238 auto unnecessaryOpCheck = [&](Operation *op) ->
bool {
239 if (
auto emiteq = dyn_cast<EmitEqualityOp>(op);
240 emiteq && emiteq.getLhs() == emiteq.getRhs()) {
241 redundantOps.push_back(op);
245 if (
auto callOp = dyn_cast<CallOp>(op);
246 callOp && callsPurposelessConstrainFunc(symbolTables, callOp)) {
247 redundantOps.push_back(op);
253 callable.walk([&](Operation *op) {
254 if (op == callable.getOperation()) {
255 return WalkResult::advance();
259 if (unnecessaryOpCheck(op)) {
260 return WalkResult::advance();
265 if (isDuplicateEliminationCandidate(op)) {
266 OperationComparator comp(op, map);
267 if (
auto it = uniqueOps.find(comp);
268 it != uniqueOps.end() && domInfo.dominates(it->getOp(), op)) {
269 redundantOps.push_back(op);
270 for (
unsigned opNum = 0; opNum < op->getNumResults(); opNum++) {
271 map[op->getResult(opNum)] = it->getOp()->getResult(opNum);
274 uniqueOps.insert(comp);
277 return WalkResult::advance();
280 DenseSet<Operation *> redundantOpSet;
281 for (Operation *op : redundantOps) {
282 redundantOpSet.insert(op);
285 SmallVector<Operation *> deadOpCandidates;
286 DenseSet<Operation *> queuedDeadOps;
287 auto enqueueDeadOpCandidate = [&](Value value) {
288 Operation *definingOp = value.getDefiningOp();
289 if (!definingOp || redundantOpSet.count(definingOp) ||
290 !queuedDeadOps.insert(definingOp).second) {
293 deadOpCandidates.push_back(definingOp);
296 for (Operation *op : redundantOps) {
297 LLVM_DEBUG(llvm::dbgs() <<
"Removing op: " << *op <<
'\n');
298 for (Value result : op->getResults()) {
299 if (!result.use_empty()) {
300 auto it = map.find(result);
302 it != map.end(),
"failed to find a replacement value for redundant operation result"
304 LLVM_DEBUG(llvm::dbgs() <<
"Replacing " << it->first <<
" with " << it->second <<
'\n');
305 result.replaceAllUsesWith(it->second);
309 SmallVector<Value> operands(op->getOperands());
311 for (Value operand : operands) {
312 enqueueDeadOpCandidate(operand);
318 while (!deadOpCandidates.empty()) {
319 Operation *op = deadOpCandidates.pop_back_val();
320 queuedDeadOps.erase(op);
321 if (!isOpTriviallyDead(op)) {
325 SmallVector<Value> operands(op->getOperands());
326 LLVM_DEBUG(llvm::dbgs() <<
"Removing dead producer: " << *op <<
'\n');
328 for (Value operand : operands) {
329 enqueueDeadOpCandidate(operand);
bool isExternal() const
Returns true if this node is an external node.
mlir::CallableOpInterface getCalledFunction() const
Returns the called function that the callable region represents.
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 OperationComparator getTombstoneKey()
static unsigned getHashValue(const OperationComparator &oc)
static bool isEqual(const OperationComparator &lhs, const OperationComparator &rhs)
static OperationComparator getEmptyKey()