25#include <mlir/Dialect/SCF/IR/SCF.h>
26#include <mlir/IR/BuiltinOps.h>
28#include <llvm/ADT/DenseMap.h>
29#include <llvm/ADT/DenseMapInfo.h>
30#include <llvm/ADT/SmallVector.h>
31#include <llvm/Support/Debug.h>
38#define GEN_PASS_DEF_REDUNDANTREADANDWRITEELIMINATIONPASS
49#define DEBUG_TYPE "llzk-redundant-read-write-pass"
57 explicit ReferenceID(Value v) {
59 if (v == llvm::DenseMapInfo<Value>::getEmptyKey() ||
60 v == llvm::DenseMapInfo<Value>::getTombstoneKey()) {
62 }
else if (
auto constVal = dyn_cast_if_present<FeltConstantOp>(v.getDefiningOp())) {
63 identifier = constVal.getValue();
64 }
else if (
auto constIdxVal = dyn_cast_if_present<arith::ConstantIndexOp>(v.getDefiningOp())) {
65 identifier = llvm::cast<IntegerAttr>(constIdxVal.getValue()).getValue();
70 explicit ReferenceID(Attribute attr) : identifier(attr) {}
71 explicit ReferenceID(
const APInt &i) : identifier(i) {}
72 explicit ReferenceID(
unsigned i) : identifier(APInt(64, i)) {}
74 bool isValue()
const {
return std::holds_alternative<Value>(identifier); }
75 bool isAttribute()
const {
return std::holds_alternative<Attribute>(identifier); }
76 bool isConst()
const {
return std::holds_alternative<APInt>(identifier); }
78 Value getValue()
const {
79 ensure(isValue(),
"does not hold Value");
80 return std::get<Value>(identifier);
83 Attribute getAttribute()
const {
84 ensure(isAttribute(),
"does not hold Attribute");
85 return std::get<Attribute>(identifier);
88 APInt getConst()
const {
89 ensure(isConst(),
"does not hold const");
90 return std::get<APInt>(identifier);
93 void print(raw_ostream &os)
const {
94 if (
const auto *v = std::get_if<Value>(&identifier)) {
95 if (
auto opres = dyn_cast<OpResult>(*v)) {
96 os <<
'%' << opres.getResultNumber();
100 }
else if (
const auto *attr = std::get_if<Attribute>(&identifier)) {
103 os << std::get<APInt>(identifier);
107 friend bool operator==(
const ReferenceID &lhs,
const ReferenceID &rhs) {
108 return lhs.identifier == rhs.identifier;
111 friend raw_ostream &
operator<<(raw_ostream &os,
const ReferenceID &
id) {
121 std::variant<Attribute, APInt, Value> identifier;
129template <>
struct DenseMapInfo<ReferenceID> {
130 static ReferenceID
getEmptyKey() {
return ReferenceID(DenseMapInfo<Value>::getEmptyKey()); }
132 return ReferenceID(DenseMapInfo<Value>::getTombstoneKey());
136 return hash_value(r.getValue());
137 }
else if (r.isAttribute()) {
138 return hash_value(r.getAttribute());
140 return hash_value(r.getConst());
142 static bool isEqual(
const ReferenceID &lhs,
const ReferenceID &rhs) {
return lhs == rhs; }
170 template <
typename IdType>
static std::shared_ptr<ReferenceNode>
create(IdType
id, Value v) {
171 ReferenceNode n(
id, v);
173 return std::make_shared<ReferenceNode>(std::move(n));
178 std::shared_ptr<ReferenceNode> clone(
bool withChildren =
true)
const {
179 ReferenceNode copy(identifier, storedValue);
180 copy.updateLastWrite(lastWrite);
182 for (
const auto &[
id, child] : children) {
183 copy.children[id] = child->clone(withChildren);
186 return std::make_shared<ReferenceNode>(std::move(copy));
189 template <
typename IdType>
190 std::shared_ptr<ReferenceNode>
191 createChild(IdType
id, Value storedVal,
const std::shared_ptr<ReferenceNode> &valTree =
nullptr) {
192 std::shared_ptr<ReferenceNode> child =
create(
id, storedVal);
193 child->setCurrentValue(storedVal, valTree);
194 children[child->identifier] = child;
200 template <
typename IdType> std::shared_ptr<ReferenceNode> getChild(IdType
id)
const {
201 auto it = children.find(ReferenceID(
id));
202 if (it != children.end()) {
211 template <
typename IdType>
212 std::shared_ptr<ReferenceNode> getOrCreateChild(IdType
id, Value storedVal =
nullptr) {
213 auto it = children.find(ReferenceID(
id));
214 if (it != children.end()) {
217 return createChild(
id, storedVal);
222 Operation *updateLastWrite(Operation *writeOp) {
223 Operation *old = lastWrite;
228 void clearLastWrite() { lastWrite =
nullptr; }
230 void setCurrentValue(Value v,
const std::shared_ptr<ReferenceNode> &valTree =
nullptr) {
232 if (valTree !=
nullptr) {
235 children = valTree->children;
239 void invalidateChildren() { children.clear(); }
241 bool invalidateNonIntegerOffsetChildren() {
242 SmallVector<ReferenceID> invalidChildren;
243 for (
const auto &[
id, _] : children) {
244 if (!
id.isAttribute() || !isa<IntegerAttr>(
id.getAttribute())) {
245 invalidChildren.push_back(
id);
248 for (
const ReferenceID &
id : invalidChildren) {
251 return !invalidChildren.empty();
254 bool isLeaf()
const {
return children.empty(); }
256 Value getStoredValue()
const {
return storedValue; }
258 bool hasStoredValue()
const {
return storedValue !=
nullptr; }
260 void print(raw_ostream &os,
int indent = 0)
const {
261 os.indent(indent) <<
'[' << identifier;
262 if (storedValue !=
nullptr) {
263 os <<
" => " << storedValue;
266 if (!children.empty()) {
268 for (
const auto &[_, child] : children) {
269 child->print(os, indent + 4);
272 os.indent(indent) <<
'}';
277 friend raw_ostream &
operator<<(raw_ostream &os,
const ReferenceNode &r) {
284 topLevelEq(
const std::shared_ptr<ReferenceNode> &lhs,
const std::shared_ptr<ReferenceNode> &rhs) {
285 return lhs->identifier == rhs->identifier && lhs->storedValue == rhs->storedValue &&
286 lhs->lastWrite == rhs->lastWrite;
289 friend std::shared_ptr<ReferenceNode> greatestCommonSubtree(
290 const std::shared_ptr<ReferenceNode> &lhs,
const std::shared_ptr<ReferenceNode> &rhs
292 if (!topLevelEq(lhs, rhs)) {
295 auto res = lhs->clone(
false);
297 for (
auto &[
id, lhsChild] : lhs->children) {
298 if (
auto it = rhs->children.find(
id); it != rhs->children.end()) {
299 auto &rhsChild = it->second;
300 if (
auto gcs = greatestCommonSubtree(lhsChild, rhsChild)) {
301 res->children[id] = gcs;
309 ReferenceID identifier;
310 mlir::Value storedValue;
311 Operation *lastWrite;
312 DenseMap<ReferenceID, std::shared_ptr<ReferenceNode>> children;
314 template <
typename IdType>
315 ReferenceNode(IdType
id, Value initialVal)
316 : identifier(std::move(id)), storedValue(initialVal), lastWrite(nullptr), children() {}
319using ValueMap = DenseMap<mlir::Value, std::shared_ptr<ReferenceNode>>;
325 DenseMap<SymbolRefAttr, Value> globals;
326 DenseMap<ReferenceID, Value> ram;
328 DenseMap<Value, Value> ramExact;
333struct BlockWriteCandidates {
334 DenseMap<SymbolRefAttr, Operation *> globals;
335 DenseMap<Value, Operation *> ram;
344ValueMap intersectValueMap(
const ValueMap &lhs,
const ValueMap &rhs) {
346 for (
const auto &[
id, lhsValTree] : lhs) {
347 if (
auto it = rhs.find(
id); it != rhs.end()) {
348 const auto &rhsValTree = it->second;
349 res[id] = greatestCommonSubtree(lhsValTree, rhsValTree);
356template <
typename KeyT>
358intersectValueLookup(
const DenseMap<KeyT, Value> &lhs,
const DenseMap<KeyT, Value> &rhs) {
359 DenseMap<KeyT, Value> res;
360 for (
const auto &[
id, lhsVal] : lhs) {
361 if (
auto it = rhs.find(
id); it != rhs.end() && it->second == lhsVal) {
369KnownState intersect(
const KnownState &lhs,
const KnownState &rhs) {
371 intersectValueMap(lhs.values, rhs.values), intersectValueLookup(lhs.globals, rhs.globals),
372 intersectValueLookup(lhs.ram, rhs.ram), intersectValueLookup(lhs.ramExact, rhs.ramExact)
378ValueMap cloneValueMap(
const ValueMap &orig) {
380 for (
const auto &[
id, tree] : orig) {
381 res[id] = tree->clone();
388KnownState cloneKnownState(
const KnownState &orig) {
389 return {cloneValueMap(orig.values), orig.globals, orig.ram, orig.ramExact};
393 using Base = RedundantReadAndWriteEliminationPassBase<PassImpl>;
401 void runOnOperation()
override {
402 getOperation().walk([&](FuncDefOp fn) { runOnFunc(fn); });
407 void runOnFunc(FuncDefOp fn) {
413 LLVM_DEBUG(llvm::dbgs() <<
"Running on " << fn.getName() <<
'\n');
416 DenseMap<Value, Value> replacementMap;
418 SmallVector<Value> readVals;
421 SmallVector<Operation *> redundantWrites;
423 KnownState initState;
425 for (
auto arg : fn.getArguments()) {
426 initState.values[arg] = ReferenceNode::create(arg, arg);
430 *fn.
getCallableRegion(), std::move(initState), replacementMap, readVals, redundantWrites
435 for (
auto &[orig, replace] : replacementMap) {
436 LLVM_DEBUG(llvm::dbgs() <<
"replacing " << orig <<
" with " << replace <<
'\n');
437 orig.replaceAllUsesWith(replace);
441 for (
auto *writeOp : redundantWrites) {
442 LLVM_DEBUG(llvm::dbgs() <<
"erase write: " << *writeOp <<
'\n');
448 for (
auto it = readVals.rbegin(); it != readVals.rend(); it++) {
450 if (readVal.use_empty()) {
451 LLVM_DEBUG(llvm::dbgs() <<
"erase read: " << readVal <<
'\n');
452 readVal.getDefiningOp()->erase();
457 KnownState runOnRegion(
458 Region &r, KnownState &&initState, DenseMap<Value, Value> &replacementMap,
459 SmallVector<Value> &readVals, SmallVector<Operation *> &redundantWrites
462 DenseMap<Block *, KnownState> endStates;
464 endStates[
nullptr] = initState;
465 auto getBlockState = [&endStates](Block *blockPtr) {
466 auto it = endStates.find(blockPtr);
467 ensure(it != endStates.end(),
"unknown end state means we have an unsupported backedge");
468 return cloneKnownState(it->second);
470 auto hasBlockState = [&endStates](Block *blockPtr) {
471 return endStates.find(blockPtr) != endStates.end();
473 std::deque<Block *> frontier;
474 DenseSet<Block *> queued;
475 DenseSet<Block *> processed;
476 auto enqueue = [&](Block *blockPtr) {
477 if (processed.find(blockPtr) == processed.end() && queued.insert(blockPtr).second) {
478 frontier.push_back(blockPtr);
483 SmallVector<KnownState> terminalStates;
484 size_t deferralsWithoutProgress = 0;
486 while (!frontier.empty()) {
487 Block *currentBlock = frontier.front();
488 frontier.pop_front();
489 queued.erase(currentBlock);
492 KnownState currentState;
493 auto it = currentBlock->pred_begin();
494 auto itEnd = currentBlock->pred_end();
497 currentState = getBlockState(
nullptr);
500 for (
auto predIt = it; predIt != itEnd; predIt++) {
501 ready &= hasBlockState(*predIt);
504 deferralsWithoutProgress++;
506 deferralsWithoutProgress <= frontier.size(),
507 "unknown end state means we have an unsupported backedge"
509 enqueue(currentBlock);
513 currentState = getBlockState(*it);
517 for (it++; it != itEnd; it++) {
518 currentState = intersect(currentState, getBlockState(*it));
523 deferralsWithoutProgress = 0;
524 auto endState = runOnBlock(
525 *currentBlock, std::move(currentState), replacementMap, readVals, redundantWrites
531 ensure(processed.find(currentBlock) == processed.end(),
"backedge");
532 endStates[currentBlock] = std::move(endState);
533 processed.insert(currentBlock);
536 if (currentBlock->hasNoSuccessors()) {
537 terminalStates.push_back(cloneKnownState(endStates[currentBlock]));
539 for (Block *succ : currentBlock->getSuccessors()) {
546 ensure(!terminalStates.empty(),
"computed no states");
547 auto finalState = terminalStates.front();
548 for (
const auto *it = terminalStates.begin() + 1; it != terminalStates.end(); it++) {
549 finalState = intersect(finalState, *it);
554 KnownState runOnBlock(
555 Block &b, KnownState &&state, DenseMap<Value, Value> &replacementMap,
556 SmallVector<Value> &readVals, SmallVector<Operation *> &redundantWrites
558 BlockWriteCandidates writeCandidates;
559 for (Operation &op : b) {
563 if (!op.getRegions().empty()) {
564 KnownState parentState = cloneKnownState(state);
569 KnownState regionEntryState = cloneKnownState(state);
570 if (isa<scf::ForOp, scf::WhileOp>(op)) {
571 regionEntryState.globals.clear();
572 regionEntryState.ram.clear();
573 regionEntryState.ramExact.clear();
575 SmallVector<KnownState> regionStates;
576 for (Region ®ion : op.getRegions()) {
577 if (region.empty()) {
580 auto regionState = runOnRegion(
581 region, cloneKnownState(regionEntryState), replacementMap, readVals, redundantWrites
583 regionStates.push_back(regionState);
585 if (regionStates.empty()) {
587 runOperation(&op, state, replacementMap, readVals, redundantWrites, writeCandidates);
588 writeCandidates.clear();
592 KnownState finalState = regionStates.front();
593 for (
const auto *it = regionStates.begin() + 1; it != regionStates.end(); it++) {
594 finalState = intersect(finalState, *it);
600 finalState.globals = intersectValueLookup(parentState.globals, finalState.globals);
601 finalState.ram = intersectValueLookup(parentState.ram, finalState.ram);
602 finalState.ramExact = intersectValueLookup(parentState.ramExact, finalState.ramExact);
603 state = std::move(finalState);
604 writeCandidates.clear();
607 runOperation(&op, state, replacementMap, readVals, redundantWrites, writeCandidates);
609 return std::move(state);
620 Operation *op, KnownState &state, DenseMap<Value, Value> &replacementMap,
621 SmallVector<Value> &readVals, SmallVector<Operation *> &redundantWrites,
622 BlockWriteCandidates &writeCandidates
627 auto translate = [&replacementMap](Value v) {
628 if (
auto it = replacementMap.find(v); it != replacementMap.end()) {
635 auto tryGetValTree = [&state](Value v) -> std::shared_ptr<ReferenceNode> {
636 if (
auto it = state.values.find(v); it != state.values.end()) {
642 auto doStatefulRead =
643 [&]<
typename KeyT>(Value resVal, DenseMap<KeyT, Value> &knownValues,
const KeyT &key) {
644 if (
auto it = knownValues.find(key); it != knownValues.end()) {
645 replacementMap[resVal] = it->second;
646 readVals.push_back(resVal);
649 knownValues[key] = resVal;
650 state.values[resVal] = ReferenceNode::create(resVal, resVal);
652 readVals.push_back(resVal);
657 const IntegerAttr zeroTableOffset = IntegerAttr::get(IndexType::get(op->getContext()), 0);
658 auto getMemberNode = [&](Value
component, FlatSymbolRefAttr member) {
659 std::shared_ptr<ReferenceNode> componentNode = tryGetValTree(translate(
component));
660 if (componentNode ==
nullptr) {
661 return std::shared_ptr<ReferenceNode>();
663 return componentNode->getOrCreateChild(member);
665 auto getMemberAccessNode = [&](MemberReadOp readm) {
666 std::shared_ptr<ReferenceNode> access =
667 getMemberNode(readm.getComponent(), readm.getMemberNameAttr());
668 if (access ==
nullptr) {
671 access = access->getOrCreateChild(readm.getTableOffset().value_or(zeroTableOffset));
672 if (!readm.getMapOperands().empty()) {
673 access = access->getOrCreateChild(readm.getMapOpGroupSizesAttr());
674 access = access->getOrCreateChild(readm.getNumDimsPerMapAttr());
676 for (
auto mapOperands : readm.getMapOperands()) {
677 for (Value operand : mapOperands) {
678 access = access->getOrCreateChild(translate(operand));
686 auto doArrayReadLike = [&]<HasInterface<ArrayAccessOpInterface> OpClass>(OpClass readarr) {
687 Value resVal = readarr.getResult();
688 std::shared_ptr<ReferenceNode> currValTree = tryGetValTree(translate(readarr.getArrRef()));
689 if (currValTree ==
nullptr) {
690 state.values[resVal] = ReferenceNode::create(resVal, resVal);
691 readVals.push_back(resVal);
695 for (Value origIdx : readarr.getIndices()) {
696 Value idxVal = translate(origIdx);
697 currValTree = currValTree->getOrCreateChild(idxVal);
700 if (!currValTree->hasStoredValue()) {
701 currValTree->setCurrentValue(resVal);
704 if (currValTree->getStoredValue() != resVal) {
706 llvm::dbgs() << readarr.getOperationName() <<
": replace " << resVal <<
" with "
707 << currValTree->getStoredValue() <<
'\n'
709 replacementMap[resVal] = currValTree->getStoredValue();
711 state.values[resVal] = currValTree;
713 llvm::dbgs() << readarr.getOperationName() <<
": " << resVal <<
" => " << *currValTree
718 readVals.push_back(resVal);
726 auto doArrayWriteLike = [&]<HasInterface<ArrayAccessOpInterface> OpClass>(OpClass writearr) {
727 std::shared_ptr<ReferenceNode> currValTree = tryGetValTree(translate(writearr.getArrRef()));
728 if (currValTree ==
nullptr) {
731 Value newVal = translate(writearr.getRvalue());
732 std::shared_ptr<ReferenceNode> valTree = tryGetValTree(newVal);
734 for (Value origIdx : writearr.getIndices()) {
735 Value idxVal = translate(origIdx);
738 if (ReferenceID(idxVal).isValue()) {
739 LLVM_DEBUG(llvm::dbgs() << writearr.getOperationName() <<
": invalidate alias\n");
740 currValTree->invalidateChildren();
742 currValTree = currValTree->getOrCreateChild(idxVal);
745 if (currValTree->getStoredValue() == newVal) {
747 llvm::dbgs() << writearr.getOperationName() <<
": subsequent " << writearr
750 redundantWrites.push_back(writearr);
752 if (Operation *lastWrite = currValTree->updateLastWrite(writearr)) {
754 llvm::dbgs() << writearr.getOperationName() <<
"writearr: replacing " << lastWrite
755 <<
" with prior write " << *lastWrite <<
'\n'
757 redundantWrites.push_back(lastWrite);
759 currValTree->setCurrentValue(newVal, valTree);
764 if (
auto readGlobal = dyn_cast<global::GlobalReadOp>(op)) {
765 const auto name = readGlobal.getNameRef();
766 if (!doStatefulRead(readGlobal.getVal(), state.globals, name)) {
767 writeCandidates.globals.erase(name);
769 }
else if (
auto writeGlobal = dyn_cast<global::GlobalWriteOp>(op)) {
770 const auto name = writeGlobal.getNameRef();
771 Value value = translate(writeGlobal.getVal());
772 if (
auto known = state.globals.find(name);
773 known != state.globals.end() && known->second == value) {
774 redundantWrites.push_back(writeGlobal.getOperation());
776 if (
auto previous = writeCandidates.globals.find(name);
777 previous != writeCandidates.globals.end()) {
778 redundantWrites.push_back(previous->second);
780 state.globals[name] = value;
781 writeCandidates.globals[name] = writeGlobal.getOperation();
785 else if (
auto load = dyn_cast<ram::LoadOp>(op)) {
786 Value address = translate(load.getAddr());
787 if (!doStatefulRead(load.getVal(), state.ram, ReferenceID(address))) {
788 writeCandidates.ram.clear();
790 state.ramExact[address] = translate(load.getVal());
791 }
else if (
auto store = dyn_cast<ram::StoreOp>(op)) {
792 Value address = translate(store.getAddr());
793 Value value = translate(store.getVal());
794 if (
auto known = state.ramExact.find(address);
795 known != state.ramExact.end() && known->second == value) {
796 redundantWrites.push_back(store.getOperation());
798 if (
auto previous = writeCandidates.ram.find(address);
799 previous != writeCandidates.ram.end()) {
800 redundantWrites.push_back(previous->second);
802 writeCandidates.ram[address] = store.getOperation();
804 state.ramExact.clear();
805 state.ram[ReferenceID(address)] = value;
806 state.ramExact[address] = value;
810 else if (
auto newStruct = dyn_cast<CreateStructOp>(op)) {
812 auto structVal = ReferenceNode::create(newStruct, newStruct);
813 state.values[newStruct] = structVal;
815 llvm::dbgs() << newStruct.getOperationName() <<
": " << *state.values[newStruct] <<
'\n'
818 readVals.push_back(newStruct);
819 }
else if (
auto readm = dyn_cast<MemberReadOp>(op)) {
820 std::shared_ptr<ReferenceNode> access = getMemberAccessNode(readm);
821 Value resVal = readm.getVal();
822 if (access ==
nullptr) {
823 state.values[resVal] = ReferenceNode::create(resVal, resVal);
824 readVals.push_back(resVal);
827 if (!access->hasStoredValue()) {
828 access->setCurrentValue(resVal);
830 if (access->getStoredValue() != resVal) {
832 llvm::dbgs() << readm.getOperationName() <<
": adding replacement map entry { "
833 << resVal <<
" => " << access->getStoredValue() <<
" }\n"
835 replacementMap[resVal] = access->getStoredValue();
837 state.values[resVal] = access;
838 LLVM_DEBUG(llvm::dbgs() << readm.getOperationName() <<
": " << *access <<
'\n');
840 readVals.push_back(resVal);
841 }
else if (
auto writem = dyn_cast<MemberWriteOp>(op)) {
842 std::shared_ptr<ReferenceNode> member =
843 getMemberNode(writem.getComponent(), writem.getMemberNameAttr());
844 if (member ==
nullptr) {
849 bool invalidatedMayAliasRead = member->invalidateNonIntegerOffsetChildren();
850 Value writeVal = translate(writem.getVal());
851 auto valTree = tryGetValTree(writeVal);
853 auto access = member->getOrCreateChild(zeroTableOffset);
854 if (invalidatedMayAliasRead) {
855 access->clearLastWrite();
857 if (access->getStoredValue() == writeVal) {
859 llvm::dbgs() << writem.getOperationName() <<
": recording redundant write " << writem
862 redundantWrites.push_back(writem);
864 if (
auto *lastWrite = access->updateLastWrite(writem)) {
866 llvm::dbgs() << writem.getOperationName() <<
": recording overwritten write "
867 << *lastWrite <<
'\n'
869 redundantWrites.push_back(lastWrite);
871 access->setCurrentValue(writeVal, valTree);
873 llvm::dbgs() << writem.getOperationName() <<
": " << *access <<
" set to " << writeVal
879 else if (
auto newArray = dyn_cast<CreateArrayOp>(op)) {
880 auto arrayVal = ReferenceNode::create(newArray, newArray);
881 state.values[newArray] = arrayVal;
886 for (
auto elem : newArray.getElements()) {
887 Value elemVal = translate(elem);
888 auto valTree = tryGetValTree(elemVal);
889 auto elemChild = arrayVal->createChild(idx, elemVal, valTree);
891 llvm::dbgs() << newArray.getOperationName() <<
": element " << idx <<
" initialized to "
892 << *elemChild <<
'\n'
897 readVals.push_back(newArray);
898 }
else if (
auto readarr = dyn_cast<ReadArrayOp>(op)) {
899 doArrayReadLike(readarr);
900 }
else if (
auto writearr = dyn_cast<WriteArrayOp>(op)) {
901 doArrayWriteLike(writearr);
902 }
else if (
auto extractarr = dyn_cast<ExtractArrayOp>(op)) {
904 doArrayReadLike(extractarr);
905 }
else if (
auto insertarr = dyn_cast<InsertArrayOp>(op)) {
907 doArrayWriteLike(insertarr);
909 state.globals.clear();
911 state.ramExact.clear();
912 writeCandidates.clear();
916 writeCandidates.clear();
void print(llvm::raw_ostream &os) const
::mlir::Region * getCallableRegion()
Required by FunctionOpInterface.
void ensure(bool condition, const llvm::Twine &errMsg)
Interval operator<<(const Interval &lhs, const Interval &rhs)
bool hasReadEffect(mlir::Operation *op)
Returns true when op has a memory read effect.
bool hasUnknownOrNonReadEffect(mlir::Operation *op)
Returns true when op may have an unknown effect or any effect other than memory read.
mlir::Operation * create(MlirOpBuilder cBuilder, MlirLocation cLocation, Args &&...args)
Creates a new operation using an ODS build method.
static bool isEqual(const ReferenceID &lhs, const ReferenceID &rhs)
static ReferenceID getEmptyKey()
static ReferenceID getTombstoneKey()
static unsigned getHashValue(const ReferenceID &r)