29#include <mlir/Dialect/SCF/IR/SCF.h>
30#include <mlir/IR/BuiltinOps.h>
32#include <llvm/ADT/ArrayRef.h>
33#include <llvm/ADT/DenseMap.h>
34#include <llvm/ADT/DenseMapInfo.h>
35#include <llvm/ADT/DenseSet.h>
36#include <llvm/ADT/SmallVector.h>
37#include <llvm/Support/Debug.h>
45#define GEN_PASS_DEF_REDUNDANTREADANDWRITEELIMINATIONPASS
56#define DEBUG_TYPE "llzk-redundant-read-write-pass"
64 explicit ReferenceID(Value v) {
65 if (
auto constVal = dyn_cast_if_present<FeltConstantOp>(v.getDefiningOp())) {
66 identifier = constVal.getValue();
67 }
else if (
auto constIdxVal = dyn_cast_if_present<arith::ConstantIndexOp>(v.getDefiningOp())) {
68 identifier = llvm::DynamicAPInt(constIdxVal.value());
73 explicit ReferenceID(Attribute attr) : identifier(attr) {}
74 explicit ReferenceID(
const llvm::DynamicAPInt &i) : identifier(i) {}
75 explicit ReferenceID(
unsigned i) : identifier(llvm::DynamicAPInt(i)) {}
77 bool isValue()
const {
return std::holds_alternative<Value>(identifier); }
78 bool isAttribute()
const {
return std::holds_alternative<Attribute>(identifier); }
79 bool isConst()
const {
return std::holds_alternative<llvm::DynamicAPInt>(identifier); }
81 Value getValue()
const {
82 ensure(isValue(),
"does not hold Value");
83 return std::get<Value>(identifier);
86 Attribute getAttribute()
const {
87 ensure(isAttribute(),
"does not hold Attribute");
88 return std::get<Attribute>(identifier);
91 llvm::DynamicAPInt getConst()
const {
92 ensure(isConst(),
"does not hold const");
93 return std::get<llvm::DynamicAPInt>(identifier);
96 void print(raw_ostream &os)
const {
97 if (
const auto *v = std::get_if<Value>(&identifier)) {
98 if (
auto opres = dyn_cast<OpResult>(*v)) {
99 os <<
'%' << opres.getResultNumber();
103 }
else if (
const auto *attr = std::get_if<Attribute>(&identifier)) {
106 os << std::get<llvm::DynamicAPInt>(identifier);
110 friend bool operator==(
const ReferenceID &lhs,
const ReferenceID &rhs) {
111 return lhs.identifier == rhs.identifier;
114 friend raw_ostream &
operator<<(raw_ostream &os,
const ReferenceID &
id) {
124 std::variant<Attribute, llvm::DynamicAPInt, Value> identifier;
132template <>
struct DenseMapInfo<ReferenceID> {
135 return hash_value(r.getValue());
136 }
else if (r.isAttribute()) {
137 return hash_value(r.getAttribute());
141 static bool isEqual(
const ReferenceID &lhs,
const ReferenceID &rhs) {
return lhs == rhs; }
166 struct AggregateSnapshotSource {
172 struct AggregateReadOrigin {
173 std::shared_ptr<ReferenceNode> storage;
174 uint64_t mutationEpoch;
178 template <
typename IdType>
static std::shared_ptr<ReferenceNode>
create(IdType
id, Value v) {
179 ReferenceNode n(
id, v);
181 return std::make_shared<ReferenceNode>(std::move(n));
186 std::shared_ptr<ReferenceNode> clone(
bool withChildren =
true)
const {
187 ReferenceNode copy(identifier, storedValue);
188 copy.updateLastWrite(lastWrite);
189 copy.aggregateMutationEpoch = aggregateMutationEpoch;
191 copy.dynamicChildCount = dynamicChildCount;
192 for (
const auto &[
id, child] : children) {
193 copy.children[id] = child->clone(withChildren);
196 return std::make_shared<ReferenceNode>(std::move(copy));
204 std::shared_ptr<ReferenceNode> cloneForValueCopy(Value copiedValue)
const {
206 copy->identifier = ReferenceID(copiedValue);
207 copy->storedValue = copiedValue;
208 copy->clearLastWritesInSubtree();
212 template <
typename IdType>
213 std::shared_ptr<ReferenceNode>
214 createChild(IdType
id, Value storedVal,
const std::shared_ptr<ReferenceNode> &valTree =
nullptr) {
215 std::shared_ptr<ReferenceNode> child =
create(
id, storedVal);
216 child->setCurrentValue(storedVal, valTree);
217 if (child->identifier.isValue() && children.find(child->identifier) == children.end()) {
220 children[child->identifier] = child;
226 template <
typename IdType> std::shared_ptr<ReferenceNode> getChild(IdType
id)
const {
227 auto it = children.find(ReferenceID(
id));
228 if (it != children.end()) {
237 template <
typename IdType>
238 std::shared_ptr<ReferenceNode> getOrCreateChild(IdType
id, Value storedVal =
nullptr) {
239 auto it = children.find(ReferenceID(
id));
240 if (it != children.end()) {
243 return createChild(
id, storedVal);
248 Operation *updateLastWrite(Operation *writeOp) {
249 Operation *old = lastWrite;
254 void clearLastWrite() { lastWrite =
nullptr; }
263 void clearLastWritesObservedBy(ArrayRef<ReferenceID> indices) {
264 if (indices.empty()) {
265 clearLastWritesInSubtree();
270 const ReferenceID &index = indices.front();
271 ArrayRef<ReferenceID> remaining = indices.drop_front();
272 if (!index.isConst()) {
273 for (
const auto &[
id, child] : children) {
274 if (!
id.isAttribute()) {
275 child->clearLastWritesObservedBy(remaining);
281 if (
auto it = children.find(index); it != children.end()) {
282 it->second->clearLastWritesObservedBy(remaining);
284 for (
const auto &[
id, child] : children) {
286 child->clearLastWritesObservedBy(remaining);
295 void clearLastWritesInSubtree() {
296 for (
const auto &[_, child] : children) {
297 child->clearLastWritesInSubtree();
302 void setCurrentValue(Value v,
const std::shared_ptr<ReferenceNode> &valTree =
nullptr) {
304 reusableAggregateRead.reset();
305 aggregateSnapshotSource.reset();
306 aggregateReadOrigin.reset();
307 if (valTree !=
nullptr) {
309 children = valTree->children;
310 dynamicChildCount = valTree->dynamicChildCount;
315 invalidateChildren();
319 void invalidateChildren() {
321 dynamicChildCount = 0;
328 void invalidateDynamicChildren() {
329 if (dynamicChildCount == 0) {
332 SmallVector<ReferenceID> invalidChildren;
333 for (
const auto &[
id, _] : children) {
335 invalidChildren.push_back(
id);
338 for (
const ReferenceID &
id : invalidChildren) {
341 dynamicChildCount = 0;
344 bool invalidateNonIntegerOffsetChildren() {
345 SmallVector<ReferenceID> invalidChildren;
346 size_t invalidDynamicChildCount = 0;
347 for (
const auto &[
id, _] : children) {
348 if (!
id.isAttribute() || !isa<IntegerAttr>(
id.getAttribute())) {
349 invalidChildren.push_back(
id);
351 ++invalidDynamicChildCount;
355 for (
const ReferenceID &
id : invalidChildren) {
358 dynamicChildCount -= invalidDynamicChildCount;
359 return !invalidChildren.empty();
362 bool isLeaf()
const {
return children.empty(); }
364 Value getStoredValue()
const {
return storedValue; }
366 bool hasStoredValue()
const {
return storedValue !=
nullptr; }
369 const std::optional<Value> &getReusableAggregateRead()
const {
return reusableAggregateRead; }
372 void setReusableAggregateRead(Value value) { reusableAggregateRead = value; }
375 const std::optional<AggregateSnapshotSource> &getAggregateSnapshotSource()
const {
376 return aggregateSnapshotSource;
380 void setAggregateSnapshotSource(Value value, Operation *write) {
381 aggregateSnapshotSource = AggregateSnapshotSource {.value = value, .write = write};
385 void clearAggregateSnapshotSource() { aggregateSnapshotSource.reset(); }
388 void setAggregateReadOrigin(
const std::shared_ptr<ReferenceNode> &storage, Operation *read) {
389 aggregateReadOrigin = AggregateReadOrigin {
391 .mutationEpoch = storage->aggregateMutationEpoch,
397 const std::optional<AggregateReadOrigin> &getAggregateReadOrigin()
const {
398 return aggregateReadOrigin;
406 void markAggregateMutation() {
407 ++aggregateMutationEpoch;
408 reusableAggregateRead.reset();
411 uint64_t getAggregateMutationEpoch()
const {
return aggregateMutationEpoch; }
413 void print(raw_ostream &os,
int indent = 0)
const {
414 os.indent(indent) <<
'[' << identifier;
415 if (storedValue !=
nullptr) {
416 os <<
" => " << storedValue;
419 if (!children.empty()) {
421 for (
const auto &[_, child] : children) {
422 child->print(os, indent + 4);
425 os.indent(indent) <<
'}';
430 friend raw_ostream &
operator<<(raw_ostream &os,
const ReferenceNode &r) {
437 topLevelEq(
const std::shared_ptr<ReferenceNode> &lhs,
const std::shared_ptr<ReferenceNode> &rhs) {
438 return lhs->identifier == rhs->identifier && lhs->storedValue == rhs->storedValue &&
439 lhs->lastWrite == rhs->lastWrite;
442 friend std::shared_ptr<ReferenceNode> greatestCommonSubtree(
443 const std::shared_ptr<ReferenceNode> &lhs,
const std::shared_ptr<ReferenceNode> &rhs
445 if (!topLevelEq(lhs, rhs)) {
448 auto res = lhs->clone(
false);
450 for (
auto &[
id, lhsChild] : lhs->children) {
451 if (
auto it = rhs->children.find(
id); it != rhs->children.end()) {
452 auto &rhsChild = it->second;
453 if (
auto gcs = greatestCommonSubtree(lhsChild, rhsChild)) {
454 res->children[id] = gcs;
456 ++res->dynamicChildCount;
465 ReferenceID identifier;
466 mlir::Value storedValue;
467 Operation *lastWrite;
471 std::optional<Value> reusableAggregateRead;
472 std::optional<AggregateSnapshotSource> aggregateSnapshotSource;
473 std::optional<AggregateReadOrigin> aggregateReadOrigin;
474 DenseMap<ReferenceID, std::shared_ptr<ReferenceNode>> children;
477 size_t dynamicChildCount;
478 uint64_t aggregateMutationEpoch = 0;
480 template <
typename IdType>
481 ReferenceNode(IdType
id, Value initialVal)
482 : identifier(std::move(id)), storedValue(initialVal), lastWrite(nullptr), children(),
483 dynamicChildCount(0) {}
486using ValueMap = DenseMap<mlir::Value, std::shared_ptr<ReferenceNode>>;
491bool requiresAggregateSnapshot(Type type) {
492 return isa<array::ArrayType, pod::PodType, component::StructType>(type);
502bool isTransparentAggregateAliasCast(Operation *op) {
503 if (op ==
nullptr || op->getNumOperands() != 1 || op->getNumResults() != 1 ||
504 !requiresAggregateSnapshot(op->getOperand(0).getType()) ||
505 !requiresAggregateSnapshot(op->getResult(0).getType())) {
508 return isa<polymorphic::UnifiableCastOp, UnrealizedConversionCastOp>(op);
512Value getAggregateAliasRoot(Value value) {
513 Operation *def = value.getDefiningOp();
514 while (isTransparentAggregateAliasCast(def)) {
515 value = def->getOperand(0);
516 def = value.getDefiningOp();
524 std::optional<Value> scalar;
525 std::shared_ptr<ReferenceNode> aggregate;
528using GlobalStateMap = DenseMap<SymbolRefAttr, GlobalState>;
534 GlobalStateMap globals;
535 DenseMap<ReferenceID, Value> ram;
537 DenseMap<Value, Value> ramExact;
542struct BlockWriteCandidates {
543 DenseMap<SymbolRefAttr, Operation *> globals;
544 DenseMap<Value, Operation *> ram;
553ValueMap intersectValueMap(
const ValueMap &lhs,
const ValueMap &rhs) {
555 for (
const auto &[
id, lhsValTree] : lhs) {
559 if (
auto it = rhs.find(
id); it != rhs.end() && it->second) {
561 if (
auto common = greatestCommonSubtree(lhsValTree, it->second)) {
562 res[id] = std::move(common);
572GlobalStateMap intersectGlobals(
const GlobalStateMap &lhs,
const GlobalStateMap &rhs) {
574 for (
const auto &[
id, lhsState] : lhs) {
575 if (!lhsState.scalar) {
578 if (
auto it = rhs.find(
id); it != rhs.end() && it->second.scalar == lhsState.scalar) {
579 res[id].scalar = lhsState.scalar;
586template <
typename KeyT>
588intersectValueLookup(
const DenseMap<KeyT, Value> &lhs,
const DenseMap<KeyT, Value> &rhs) {
589 DenseMap<KeyT, Value> res;
590 for (
const auto &[
id, lhsVal] : lhs) {
591 if (
auto it = rhs.find(
id); it != rhs.end() && it->second == lhsVal) {
599KnownState intersect(
const KnownState &lhs,
const KnownState &rhs) {
601 intersectValueMap(lhs.values, rhs.values), intersectGlobals(lhs.globals, rhs.globals),
602 intersectValueLookup(lhs.ram, rhs.ram), intersectValueLookup(lhs.ramExact, rhs.ramExact)
608ValueMap cloneValueMap(
const ValueMap &orig) {
610 for (
const auto &[
id, tree] : orig) {
611 res[id] = tree->clone();
616GlobalStateMap cloneGlobalStateMap(
const GlobalStateMap &orig) {
618 for (
const auto &[name,
global] : orig) {
621 res[name].aggregate =
global.aggregate->clone();
629KnownState cloneKnownState(
const KnownState &orig) {
630 return {cloneValueMap(orig.values), cloneGlobalStateMap(orig.globals), orig.ram, orig.ramExact};
640 DenseSet<Value> aggregateWriteTargets;
647 void runOnOperation()
override {
648 getOperation().walk([
this](
FuncDefOp fn) { runOnFunc(fn); });
659 LLVM_DEBUG(llvm::dbgs() <<
"Running on " << fn.getName() <<
'\n');
661 aggregateWriteTargets.clear();
662 auto recordAggregateWriteTarget = [
this](Value target) {
663 if (requiresAggregateSnapshot(target.getType())) {
664 aggregateWriteTargets.insert(getAggregateAliasRoot(target));
667 fn.walk([&](Operation *op) {
668 bool knownAggregateWrite =
false;
669 if (
auto memberWrite = dyn_cast<MemberWriteOp>(op)) {
670 recordAggregateWriteTarget(memberWrite.getComponent());
671 knownAggregateWrite =
true;
673 auto arrayAccess = llvm::dyn_cast<ArrayAccessOpInterface>(op);
674 arrayAccess && !arrayAccess.isRead()
676 recordAggregateWriteTarget(arrayAccess.getArrRef());
677 knownAggregateWrite =
true;
678 }
else if (
auto podWrite = dyn_cast<pod::WritePodOp>(op)) {
679 recordAggregateWriteTarget(podWrite.getPodRef());
680 knownAggregateWrite =
true;
681 }
else if (isa<global::GlobalWriteOp, ram::StoreOp, CallOp>(op)) {
683 knownAggregateWrite =
true;
688 for (Value operand : op->getOperands()) {
689 recordAggregateWriteTarget(operand);
695 DenseMap<Value, Value> replacementMap;
697 SmallVector<Value> readVals;
700 SmallVector<Operation *> redundantWrites;
702 KnownState initState;
704 for (
auto arg : fn.getArguments()) {
705 initState.values[getAggregateAliasRoot(arg)] = ReferenceNode::create(arg, arg);
709 *fn.
getCallableRegion(), std::move(initState), replacementMap, readVals, redundantWrites
714 for (
auto &[orig, replace] : replacementMap) {
715 LLVM_DEBUG(llvm::dbgs() <<
"replacing " << orig <<
" with " << replace <<
'\n');
716 orig.replaceAllUsesWith(replace);
720 for (
auto *writeOp : redundantWrites) {
721 LLVM_DEBUG(llvm::dbgs() <<
"erase write: " << *writeOp <<
'\n');
727 for (
auto it = readVals.rbegin(); it != readVals.rend(); it++) {
729 if (readVal.use_empty()) {
730 LLVM_DEBUG(llvm::dbgs() <<
"erase read: " << readVal <<
'\n');
731 readVal.getDefiningOp()->erase();
736 KnownState runOnRegion(
737 Region &r, KnownState &&initState, DenseMap<Value, Value> &replacementMap,
738 SmallVector<Value> &readVals, SmallVector<Operation *> &redundantWrites
741 DenseMap<Block *, KnownState> endStates;
743 endStates[
nullptr] = initState;
744 auto getBlockState = [&endStates](Block *blockPtr) {
745 auto it = endStates.find(blockPtr);
746 ensure(it != endStates.end(),
"unknown end state means we have an unsupported backedge");
747 return cloneKnownState(it->second);
749 auto hasBlockState = [&endStates](Block *blockPtr) {
750 return endStates.find(blockPtr) != endStates.end();
752 std::deque<Block *> frontier;
753 DenseSet<Block *> queued;
754 DenseSet<Block *> processed;
755 auto enqueue = [&](Block *blockPtr) {
756 if (processed.find(blockPtr) == processed.end() && queued.insert(blockPtr).second) {
757 frontier.push_back(blockPtr);
762 SmallVector<KnownState> terminalStates;
763 size_t deferralsWithoutProgress = 0;
765 while (!frontier.empty()) {
766 Block *currentBlock = frontier.front();
767 frontier.pop_front();
768 queued.erase(currentBlock);
771 KnownState currentState;
772 auto it = currentBlock->pred_begin();
773 auto itEnd = currentBlock->pred_end();
776 currentState = getBlockState(
nullptr);
779 for (
auto predIt = it; predIt != itEnd; predIt++) {
780 ready &= hasBlockState(*predIt);
783 deferralsWithoutProgress++;
785 deferralsWithoutProgress <= frontier.size(),
786 "unknown end state means we have an unsupported backedge"
788 enqueue(currentBlock);
792 currentState = getBlockState(*it);
796 for (it++; it != itEnd; it++) {
797 currentState = intersect(currentState, getBlockState(*it));
802 deferralsWithoutProgress = 0;
803 auto endState = runOnBlock(
804 *currentBlock, std::move(currentState), replacementMap, readVals, redundantWrites
810 ensure(processed.find(currentBlock) == processed.end(),
"backedge");
811 endStates[currentBlock] = std::move(endState);
812 processed.insert(currentBlock);
815 if (currentBlock->hasNoSuccessors()) {
816 terminalStates.push_back(cloneKnownState(endStates[currentBlock]));
818 for (Block *succ : currentBlock->getSuccessors()) {
825 ensure(!terminalStates.empty(),
"computed no states");
826 auto finalState = terminalStates.front();
827 for (
const auto *it = terminalStates.begin() + 1; it != terminalStates.end(); it++) {
828 finalState = intersect(finalState, *it);
833 KnownState runOnBlock(
834 Block &b, KnownState &&state, DenseMap<Value, Value> &replacementMap,
835 SmallVector<Value> &readVals, SmallVector<Operation *> &redundantWrites
837 BlockWriteCandidates writeCandidates;
838 auto clearTreeWriteCandidates = [](KnownState &knownState) {
839 for (
auto &[_, valueTree] : knownState.values) {
841 valueTree->clearLastWritesInSubtree();
846 for (Operation &op : b) {
850 if (!op.getRegions().empty()) {
851 KnownState parentState = cloneKnownState(state);
856 KnownState regionEntryState = cloneKnownState(state);
861 clearTreeWriteCandidates(regionEntryState);
862 if (isa<scf::ForOp, scf::WhileOp>(op)) {
863 regionEntryState.values.clear();
864 regionEntryState.globals.clear();
865 regionEntryState.ram.clear();
866 regionEntryState.ramExact.clear();
868 SmallVector<KnownState> regionStates;
869 for (Region ®ion : op.getRegions()) {
870 if (region.empty()) {
873 auto regionState = runOnRegion(
874 region, cloneKnownState(regionEntryState), replacementMap, readVals, redundantWrites
876 regionStates.push_back(regionState);
878 if (regionStates.empty()) {
880 runOperation(&op, state, replacementMap, readVals, redundantWrites, writeCandidates);
881 writeCandidates.clear();
885 KnownState finalState = regionStates.front();
886 for (
const auto *it = regionStates.begin() + 1; it != regionStates.end(); it++) {
887 finalState = intersect(finalState, *it);
893 finalState.values = intersectValueMap(parentState.values, finalState.values);
894 finalState.globals = intersectGlobals(parentState.globals, finalState.globals);
895 finalState.ram = intersectValueLookup(parentState.ram, finalState.ram);
896 finalState.ramExact = intersectValueLookup(parentState.ramExact, finalState.ramExact);
900 clearTreeWriteCandidates(finalState);
901 state = std::move(finalState);
902 writeCandidates.clear();
905 runOperation(&op, state, replacementMap, readVals, redundantWrites, writeCandidates);
907 return std::move(state);
918 Operation *op, KnownState &state, DenseMap<Value, Value> &replacementMap,
919 SmallVector<Value> &readVals, SmallVector<Operation *> &redundantWrites,
920 BlockWriteCandidates &writeCandidates
925 auto translate = [&replacementMap](Value v) {
926 if (
auto it = replacementMap.find(v); it != replacementMap.end()) {
936 auto canonicalStateKey = [&](Value v) {
938 Value normalized = getAggregateAliasRoot(translate(v));
939 if (normalized == v) {
947 auto tryGetValTree = [&state, &canonicalStateKey](Value v) -> std::shared_ptr<ReferenceNode> {
948 if (
auto it = state.values.find(canonicalStateKey(v)); it != state.values.end()) {
954 auto setValTree = [&state, &canonicalStateKey](Value v, std::shared_ptr<ReferenceNode> tree) {
955 state.values[canonicalStateKey(v)] = std::move(tree);
957 auto eraseValTree = [&state, &canonicalStateKey](Value v) {
958 state.values.erase(canonicalStateKey(v));
961 auto doStatefulRead =
962 [&]<
typename KeyT>(Value resVal, DenseMap<KeyT, Value> &knownValues,
const KeyT &key) {
963 readVals.push_back(resVal);
964 if (
auto it = knownValues.find(key); it != knownValues.end()) {
965 replacementMap[resVal] = it->second;
968 knownValues[key] = resVal;
969 setValTree(resVal, ReferenceNode::create(resVal, resVal));
974 auto copiedValueTree = [&](Value value) {
975 if (
auto tree = tryGetValTree(value); tree && requiresAggregateSnapshot(value.getType())) {
976 auto copy = tree->cloneForValueCopy(value);
979 tree->clearLastWritesInSubtree();
982 return tryGetValTree(value);
985 auto readAggregateSnapshot = [](
const std::shared_ptr<ReferenceNode> &tree, Value result,
987 auto copy = tree->cloneForValueCopy(result);
988 copy->setAggregateReadOrigin(tree, read);
991 tree->clearLastWritesInSubtree();
995 auto useMutatesAggregate = [](OpOperand &
use) {
996 Value value =
use.get();
997 Operation *user =
use.getOwner();
998 if (
auto memberWrite = dyn_cast<MemberWriteOp>(user)) {
999 return memberWrite.getComponent() == value;
1001 if (
auto arrayAccess = llvm::dyn_cast<ArrayAccessOpInterface>(user)) {
1002 return !arrayAccess.isRead() && arrayAccess.getArrRef() == value;
1004 if (
auto podWrite = dyn_cast<pod::WritePodOp>(user)) {
1005 return podWrite.getPodRef() == value;
1007 if (isa<global::GlobalWriteOp, ram::StoreOp, CallOp>(user)) {
1011 if (isa<constrain::ConstraintOpInterface>(user)) {
1023 auto mayMutateThroughAlias = [&](Value value,
auto &&isRelevantUse) {
1024 SmallVector<Value> worklist = {getAggregateAliasRoot(value)};
1025 DenseSet<Value> visited;
1026 while (!worklist.empty()) {
1027 Value alias = worklist.pop_back_val();
1028 if (!visited.insert(alias).second) {
1031 for (OpOperand &
use : alias.getUses()) {
1032 Operation *user =
use.getOwner();
1033 if (isTransparentAggregateAliasCast(user)) {
1034 worklist.push_back(user->getResult(0));
1037 if (useMutatesAggregate(
use) && isRelevantUse(user)) {
1045 auto mayMutateAfter = [&](Value value, Operation *point) {
1046 return mayMutateThroughAlias(value, [point](Operation *user) {
1049 return user->getBlock() != point->getBlock() || point->isBeforeInBlock(user);
1053 auto mayMutateBetween = [&](Value value, Operation *before, Operation *after) {
1054 return mayMutateThroughAlias(value, [before, after](Operation *user) {
1055 return user->getBlock() != before->getBlock() || user->getBlock() != after->getBlock() ||
1056 (before->isBeforeInBlock(user) && user->isBeforeInBlock(after));
1065 auto tryForwardAggregateSnapshot = [&](
const std::shared_ptr<ReferenceNode> &tree, Value result,
1066 Operation *read) -> std::shared_ptr<ReferenceNode> {
1067 const auto &snapshotSource = tree->getAggregateSnapshotSource();
1068 if (!snapshotSource || mayMutateBetween(snapshotSource->value, snapshotSource->write, read) ||
1069 mayMutateAfter(snapshotSource->value, read) || mayMutateAfter(result, read)) {
1072 if (
auto sourceTree = tryGetValTree(snapshotSource->value)) {
1073 replacementMap[result] = snapshotSource->value;
1083 auto tryReuseAggregateRead = [&](
const std::shared_ptr<ReferenceNode> &tree, Value result,
1084 Operation *read) -> std::shared_ptr<ReferenceNode> {
1085 const auto &candidate = tree->getReusableAggregateRead();
1089 Operation *candidateRead = candidate->getDefiningOp();
1090 if (candidateRead ==
nullptr || mayMutateBetween(*candidate, candidateRead, read) ||
1091 mayMutateAfter(*candidate, read) || mayMutateAfter(result, read)) {
1094 if (
auto candidateTree = tryGetValTree(*candidate)) {
1095 replacementMap[result] = *candidate;
1096 return candidateTree;
1106 auto processAggregateRead = [&](
const std::shared_ptr<ReferenceNode> &storage, Value result,
1107 Operation *read,
auto &&tryReuse,
auto &&rememberRead) {
1108 auto snapshot = readAggregateSnapshot(storage, result, read);
1109 if (
auto sourceTree = tryForwardAggregateSnapshot(storage, result, read)) {
1112 if (
auto reusableTree = tryReuse(storage, result, read)) {
1113 return reusableTree;
1115 auto resultTree = std::move(snapshot);
1116 rememberRead(storage, result);
1120 auto tryStandardAggregateReuse = [&](
const std::shared_ptr<ReferenceNode> &storage,
1121 Value result, Operation *read) {
1122 return tryReuseAggregateRead(storage, result, read);
1124 auto rememberAggregateRead = [](
const std::shared_ptr<ReferenceNode> &storage, Value result) {
1125 storage->setReusableAggregateRead(result);
1130 auto isAggregateWriteTarget = [
this](Value value) {
1131 return aggregateWriteTargets.contains(getAggregateAliasRoot(value));
1133 auto tryMemberAggregateReuse = [&](
const std::shared_ptr<ReferenceNode> &storage, Value result,
1134 Operation *) -> std::shared_ptr<ReferenceNode> {
1135 const auto &candidate = storage->getReusableAggregateRead();
1136 if (!candidate || isAggregateWriteTarget(*candidate) || isAggregateWriteTarget(result)) {
1139 if (
auto candidateTree = tryGetValTree(*candidate)) {
1140 replacementMap[result] = *candidate;
1141 return candidateTree;
1145 auto rememberMemberAggregateRead = [&](
const std::shared_ptr<ReferenceNode> &storage,
1147 if (!isAggregateWriteTarget(result)) {
1148 storage->setReusableAggregateRead(result);
1157 auto isRedundantAggregateWrite =
1158 [&](
const std::optional<ReferenceNode::AggregateSnapshotSource> &previous, Value value,
1160 return previous && previous->value == value &&
1161 !mayMutateBetween(value, previous->write, write);
1164 auto isRedundantTreeWrite =
1165 [&](
const std::shared_ptr<ReferenceNode> &destination,
1166 const std::optional<ReferenceNode::AggregateSnapshotSource> &previous, Value value,
1168 if (!requiresAggregateSnapshot(value.getType())) {
1169 return destination->getStoredValue() == value;
1171 return isRedundantAggregateWrite(previous, value, write);
1177 auto commitTreeWrite = [&](
const std::shared_ptr<ReferenceNode> &destination, Value value,
1178 Operation *write,
const std::shared_ptr<ReferenceNode> &valueTree,
1179 ArrayRef<std::shared_ptr<ReferenceNode>> mutatedNodes) {
1180 for (
const auto &node : mutatedNodes) {
1181 node->markAggregateMutation();
1183 destination->clearAggregateSnapshotSource();
1184 if (Operation *lastWrite = destination->updateLastWrite(write)) {
1186 llvm::dbgs() << write->getName().getStringRef() <<
": replacing " << lastWrite
1187 <<
" with prior write " << *lastWrite <<
'\n'
1189 redundantWrites.push_back(lastWrite);
1191 destination->setCurrentValue(value, valueTree);
1192 if (requiresAggregateSnapshot(value.getType())) {
1193 destination->setAggregateSnapshotSource(value, write);
1201 auto hasUnknownEffectBetween = [](Operation *before, Operation *after) {
1202 if (before ==
nullptr || after ==
nullptr || before->getBlock() != after->getBlock() ||
1203 !before->isBeforeInBlock(after)) {
1206 for (Operation *n = before->getNextNode(); n != after; n = n->getNextNode()) {
1207 if (isa<MemberWriteOp, pod::WritePodOp, global::GlobalWriteOp, ram::StoreOp, CallOp,
1208 constrain::ConstraintOpInterface>(n) ||
1209 llvm::dyn_cast<ArrayAccessOpInterface>(n)) {
1223 auto isRedundantAggregateReadBackWrite = [&](
const std::shared_ptr<ReferenceNode> &destination,
1224 Value
source, Operation *write) {
1225 auto sourceTree = tryGetValTree(
source);
1226 if (sourceTree ==
nullptr) {
1229 const auto &origin = sourceTree->getAggregateReadOrigin();
1230 return origin && origin->storage == destination &&
1231 origin->mutationEpoch == destination->getAggregateMutationEpoch() &&
1232 !mayMutateBetween(
source, origin->read, write) &&
1233 !hasUnknownEffectBetween(origin->read, write);
1237 const IntegerAttr zeroTableOffset = IntegerAttr::get(IndexType::get(op->getContext()), 0);
1238 auto getMemberNode = [&](Value component, FlatSymbolRefAttr member) {
1239 std::shared_ptr<ReferenceNode> componentNode = tryGetValTree(translate(component));
1240 if (componentNode ==
nullptr) {
1241 return std::shared_ptr<ReferenceNode>();
1243 return componentNode->getOrCreateChild(member);
1246 auto getMemberAccessNode = [&](MemberReadOp readm) {
1247 std::shared_ptr<ReferenceNode> access =
1248 getMemberNode(readm.getComponent(), readm.getMemberNameAttr());
1249 if (access ==
nullptr) {
1252 access = access->getOrCreateChild(readm.getTableOffset().value_or(zeroTableOffset));
1253 if (!readm.getMapOperands().empty()) {
1254 access = access->getOrCreateChild(readm.getMapOpGroupSizesAttr());
1255 access = access->getOrCreateChild(readm.getNumDimsPerMapAttr());
1257 for (
auto mapOperands : readm.getMapOperands()) {
1258 for (Value operand : mapOperands) {
1259 access = access->getOrCreateChild(translate(operand));
1267 auto doArrayReadLike = [&]<HasInterface<ArrayAccessOpInterface> OpClass>(OpClass readarr) {
1268 Value resVal = readarr.getResult();
1269 std::shared_ptr<ReferenceNode> currValTree = tryGetValTree(translate(readarr.getArrRef()));
1270 if (currValTree ==
nullptr) {
1271 setValTree(resVal, ReferenceNode::create(resVal, resVal));
1272 readVals.push_back(resVal);
1276 std::shared_ptr<ReferenceNode> rootValTree = currValTree;
1277 SmallVector<ReferenceID> indices;
1278 bool hasDynamicIndex =
false;
1279 for (Value origIdx : readarr.getIndices()) {
1280 Value idxVal = translate(origIdx);
1281 ReferenceID indexId(idxVal);
1282 hasDynamicIndex |= !indexId.isConst();
1283 indices.push_back(indexId);
1284 currValTree = currValTree->getOrCreateChild(idxVal);
1287 if (requiresAggregateSnapshot(resVal.getType())) {
1288 if (hasDynamicIndex) {
1289 rootValTree->clearLastWritesObservedBy(indices);
1292 resVal, processAggregateRead(
1293 currValTree, resVal, readarr.getOperation(), tryStandardAggregateReuse,
1294 rememberAggregateRead
1297 readVals.push_back(resVal);
1301 if (!currValTree->hasStoredValue()) {
1302 currValTree->setCurrentValue(resVal);
1305 if (currValTree->getStoredValue() != resVal) {
1307 llvm::dbgs() << readarr.getOperationName() <<
": replace " << resVal <<
" with "
1308 << currValTree->getStoredValue() <<
'\n'
1310 replacementMap[resVal] = currValTree->getStoredValue();
1312 if (hasDynamicIndex) {
1313 rootValTree->clearLastWritesObservedBy(indices);
1315 setValTree(resVal, currValTree);
1317 llvm::dbgs() << readarr.getOperationName() <<
": " << resVal <<
" => " << *currValTree
1322 readVals.push_back(resVal);
1328 auto doArrayWriteLike = [&]<HasInterface<ArrayAccessOpInterface> OpClass>(OpClass writearr) {
1329 std::shared_ptr<ReferenceNode> currValTree = tryGetValTree(translate(writearr.getArrRef()));
1330 if (currValTree ==
nullptr) {
1333 Value newVal = translate(writearr.getRvalue());
1338 if (requiresAggregateSnapshot(newVal.getType())) {
1339 auto destination = currValTree;
1340 for (Value origIdx : writearr.getIndices()) {
1341 destination = destination->getChild(translate(origIdx));
1342 if (destination ==
nullptr) {
1346 if (destination !=
nullptr &&
1347 isRedundantAggregateReadBackWrite(destination, newVal, writearr.getOperation())) {
1348 redundantWrites.push_back(writearr.getOperation());
1353 std::shared_ptr<ReferenceNode> valTree = copiedValueTree(newVal);
1354 SmallVector<std::shared_ptr<ReferenceNode>> mutatedNodes = {currValTree};
1356 for (Value origIdx : writearr.getIndices()) {
1357 currValTree->clearAggregateSnapshotSource();
1358 Value idxVal = translate(origIdx);
1361 if (ReferenceID(idxVal).isConst()) {
1362 currValTree->invalidateDynamicChildren();
1364 LLVM_DEBUG(llvm::dbgs() << writearr.getOperationName() <<
": invalidate alias\n");
1365 currValTree->invalidateChildren();
1367 currValTree = currValTree->getOrCreateChild(idxVal);
1368 mutatedNodes.push_back(currValTree);
1370 std::optional<ReferenceNode::AggregateSnapshotSource> previousSnapshot =
1371 currValTree->getAggregateSnapshotSource();
1376 if (isRedundantTreeWrite(currValTree, previousSnapshot, newVal, writearr.getOperation())) {
1378 llvm::dbgs() << writearr.getOperationName() <<
": subsequent " << writearr
1379 <<
" is redundant\n"
1381 redundantWrites.push_back(writearr);
1385 commitTreeWrite(currValTree, newVal, writearr.getOperation(), valTree, mutatedNodes);
1390 if (
auto readGlobal = dyn_cast<global::GlobalReadOp>(op)) {
1391 const auto name = readGlobal.getNameRef();
1392 Value result = readGlobal.getVal();
1393 readVals.push_back(result);
1394 if (requiresAggregateSnapshot(result.getType())) {
1395 if (
auto it = state.globals.find(name); it != state.globals.end() && it->second.aggregate) {
1398 processAggregateRead(
1399 it->second.aggregate, result, op, tryStandardAggregateReuse, rememberAggregateRead
1403 auto globalState = ReferenceNode::create(result, result);
1405 result, processAggregateRead(
1406 globalState, result, op, tryStandardAggregateReuse, rememberAggregateRead
1409 state.globals[name] = GlobalState {
1410 .scalar = std::nullopt,
1411 .aggregate = std::move(globalState),
1414 writeCandidates.globals.erase(name);
1416 auto it = state.globals.find(name); it != state.globals.end() && it->second.scalar
1418 replacementMap[result] = *it->second.scalar;
1420 state.globals[name] = GlobalState {.scalar = result, .aggregate =
nullptr};
1421 setValTree(result, ReferenceNode::create(result, result));
1422 writeCandidates.globals.erase(name);
1424 }
else if (
auto writeGlobal = dyn_cast<global::GlobalWriteOp>(op)) {
1425 const auto name = writeGlobal.getNameRef();
1426 Value value = translate(writeGlobal.getVal());
1427 if (requiresAggregateSnapshot(value.getType()) && [&] {
1428 auto known = state.globals.find(name);
1429 return known != state.globals.end() && known->second.aggregate &&
1430 isRedundantAggregateReadBackWrite(
1431 known->second.aggregate, value, writeGlobal.getOperation()
1434 redundantWrites.push_back(writeGlobal.getOperation());
1435 }
else if (!requiresAggregateSnapshot(value.getType()) && [&] {
1436 auto known = state.globals.find(name);
1437 return known != state.globals.end() && known->second.scalar == value;
1439 redundantWrites.push_back(writeGlobal.getOperation());
1440 }
else if (requiresAggregateSnapshot(value.getType()) && [&] {
1441 auto known = state.globals.find(name);
1442 return known != state.globals.end() && known->second.aggregate &&
1443 isRedundantTreeWrite(
1444 known->second.aggregate, known->second.aggregate->getAggregateSnapshotSource(),
1445 value, writeGlobal.getOperation()
1448 redundantWrites.push_back(writeGlobal.getOperation());
1450 if (
auto previous = writeCandidates.globals.find(name);
1451 previous != writeCandidates.globals.end()) {
1452 redundantWrites.push_back(previous->second);
1454 if (requiresAggregateSnapshot(value.getType())) {
1455 if (
auto valueTree = copiedValueTree(value)) {
1456 valueTree->setAggregateSnapshotSource(value, writeGlobal.getOperation());
1457 state.globals[name] = GlobalState {
1458 .scalar = std::nullopt,
1459 .aggregate = std::move(valueTree),
1462 state.globals.erase(name);
1465 state.globals[name] = GlobalState {.scalar = value, .aggregate =
nullptr};
1467 writeCandidates.globals[name] = writeGlobal.getOperation();
1471 else if (
auto load = dyn_cast<ram::LoadOp>(op)) {
1472 Value address = translate(load.getAddr());
1473 if (!doStatefulRead(load.getVal(), state.ram, ReferenceID(address))) {
1474 writeCandidates.ram.clear();
1476 state.ramExact[address] = translate(load.getVal());
1477 }
else if (
auto store = dyn_cast<ram::StoreOp>(op)) {
1478 Value address = translate(store.getAddr());
1479 Value value = translate(store.getVal());
1480 if (
auto known = state.ramExact.find(address);
1481 known != state.ramExact.end() && known->second == value) {
1482 redundantWrites.push_back(store.getOperation());
1484 if (
auto previous = writeCandidates.ram.find(address);
1485 previous != writeCandidates.ram.end()) {
1486 redundantWrites.push_back(previous->second);
1488 writeCandidates.ram[address] = store.getOperation();
1490 state.ramExact.clear();
1491 state.ram[ReferenceID(address)] = value;
1492 state.ramExact[address] = value;
1496 else if (
auto newStruct = dyn_cast<CreateStructOp>(op)) {
1498 auto structVal = ReferenceNode::create(newStruct, newStruct);
1499 setValTree(newStruct, structVal);
1500 LLVM_DEBUG(llvm::dbgs() << newStruct.getOperationName() <<
": " << *structVal <<
'\n');
1502 readVals.push_back(newStruct);
1503 }
else if (
auto readm = dyn_cast<MemberReadOp>(op)) {
1504 std::shared_ptr<ReferenceNode> access = getMemberAccessNode(readm);
1505 Value resVal = readm.getVal();
1506 if (access ==
nullptr) {
1507 setValTree(resVal, ReferenceNode::create(resVal, resVal));
1508 readVals.push_back(resVal);
1511 if (requiresAggregateSnapshot(resVal.getType())) {
1513 resVal, processAggregateRead(
1514 access, resVal, readm.getOperation(), tryMemberAggregateReuse,
1515 rememberMemberAggregateRead
1518 readVals.push_back(resVal);
1521 if (!access->hasStoredValue()) {
1522 access->setCurrentValue(resVal);
1524 if (access->getStoredValue() != resVal) {
1526 llvm::dbgs() << readm.getOperationName() <<
": adding replacement map entry { "
1527 << resVal <<
" => " << access->getStoredValue() <<
" }\n"
1529 replacementMap[resVal] = access->getStoredValue();
1531 setValTree(resVal, access);
1532 LLVM_DEBUG(llvm::dbgs() << readm.getOperationName() <<
": " << *access <<
'\n');
1534 readVals.push_back(resVal);
1535 }
else if (
auto writem = dyn_cast<MemberWriteOp>(op)) {
1536 auto componentTree = tryGetValTree(translate(writem.getComponent()));
1537 std::shared_ptr<ReferenceNode> member =
1538 getMemberNode(writem.getComponent(), writem.getMemberNameAttr());
1539 if (member ==
nullptr) {
1542 Value writeVal = translate(writem.getVal());
1543 auto access = member->getOrCreateChild(zeroTableOffset);
1544 std::optional<ReferenceNode::AggregateSnapshotSource> previousSnapshot =
1545 access->getAggregateSnapshotSource();
1546 if (requiresAggregateSnapshot(writeVal.getType()) &&
1547 isRedundantAggregateReadBackWrite(access, writeVal, writem.getOperation())) {
1548 redundantWrites.push_back(writem);
1551 if (requiresAggregateSnapshot(writeVal.getType()) &&
1552 isRedundantTreeWrite(access, previousSnapshot, writeVal, writem.getOperation())) {
1553 redundantWrites.push_back(writem);
1556 if (componentTree) {
1557 componentTree->clearAggregateSnapshotSource();
1561 bool invalidatedMayAliasRead = member->invalidateNonIntegerOffsetChildren();
1562 auto valTree = copiedValueTree(writeVal);
1564 if (invalidatedMayAliasRead) {
1565 access->clearLastWrite();
1569 if (!requiresAggregateSnapshot(writeVal.getType()) &&
1570 isRedundantTreeWrite(access, previousSnapshot, writeVal, writem.getOperation())) {
1572 llvm::dbgs() << writem.getOperationName() <<
": recording redundant write " << writem
1575 redundantWrites.push_back(writem);
1579 SmallVector<std::shared_ptr<ReferenceNode>> mutatedNodes;
1580 if (componentTree) {
1581 mutatedNodes.push_back(componentTree);
1583 mutatedNodes.push_back(member);
1584 mutatedNodes.push_back(access);
1585 commitTreeWrite(access, writeVal, writem.getOperation(), valTree, mutatedNodes);
1587 llvm::dbgs() << writem.getOperationName() <<
": " << *access <<
" set to " << writeVal
1593 else if (
auto newArray = dyn_cast<CreateArrayOp>(op)) {
1594 auto arrayVal = ReferenceNode::create(newArray, newArray);
1595 setValTree(newArray, arrayVal);
1600 for (
auto elem : newArray.getElements()) {
1601 Value elemVal = translate(elem);
1602 auto valTree = copiedValueTree(elemVal);
1603 auto elemChild = arrayVal->createChild(idx, elemVal, valTree);
1605 llvm::dbgs() << newArray.getOperationName() <<
": element " << idx <<
" initialized to "
1606 << *elemChild <<
'\n'
1611 readVals.push_back(newArray);
1612 }
else if (
auto readarr = dyn_cast<ReadArrayOp>(op)) {
1613 doArrayReadLike(readarr);
1614 }
else if (
auto writearr = dyn_cast<WriteArrayOp>(op)) {
1615 doArrayWriteLike(writearr);
1616 }
else if (
auto extractarr = dyn_cast<ExtractArrayOp>(op)) {
1618 doArrayReadLike(extractarr);
1619 }
else if (
auto insertarr = dyn_cast<InsertArrayOp>(op)) {
1621 doArrayWriteLike(insertarr);
1622 }
else if (
auto newPod = dyn_cast<pod::NewPodOp>(op)) {
1623 Value podValue = newPod.getResult();
1624 auto podTree = ReferenceNode::create(podValue, podValue);
1625 setValTree(podValue, podTree);
1626 for (
const auto &record : newPod.getInitializedRecordValues()) {
1627 Value value = translate(record.value);
1628 podTree->createChild(
1629 StringAttr::get(op->getContext(), record.name), value, copiedValueTree(value)
1632 readVals.push_back(podValue);
1633 }
else if (
auto readPod = dyn_cast<pod::ReadPodOp>(op)) {
1634 Value result = readPod.getResult();
1635 auto podTree = tryGetValTree(translate(readPod.getPodRef()));
1636 if (podTree ==
nullptr) {
1637 setValTree(result, ReferenceNode::create(result, result));
1639 auto record = podTree->getOrCreateChild(readPod.getRecordNameAttr());
1640 if (requiresAggregateSnapshot(result.getType())) {
1642 result, processAggregateRead(
1643 record, result, readPod.getOperation(), tryStandardAggregateReuse,
1644 rememberAggregateRead
1647 }
else if (!record->hasStoredValue()) {
1648 record->setCurrentValue(result);
1649 setValTree(result, record);
1650 }
else if (record->getStoredValue() != result) {
1651 replacementMap[result] = record->getStoredValue();
1653 setValTree(result, record);
1656 readVals.push_back(result);
1657 }
else if (
auto writePod = dyn_cast<pod::WritePodOp>(op)) {
1658 auto podTree = tryGetValTree(translate(writePod.getPodRef()));
1659 if (podTree ==
nullptr) {
1662 Value value = translate(writePod.getValue());
1663 auto record = podTree->getOrCreateChild(writePod.getRecordNameAttr());
1664 std::optional<ReferenceNode::AggregateSnapshotSource> previousSnapshot =
1665 record->getAggregateSnapshotSource();
1666 if (requiresAggregateSnapshot(value.getType()) &&
1667 isRedundantAggregateReadBackWrite(record, value, writePod.getOperation())) {
1668 redundantWrites.push_back(writePod.getOperation());
1671 if (requiresAggregateSnapshot(value.getType()) &&
1672 isRedundantTreeWrite(record, previousSnapshot, value, writePod.getOperation())) {
1673 redundantWrites.push_back(writePod.getOperation());
1676 podTree->clearAggregateSnapshotSource();
1678 if (!requiresAggregateSnapshot(value.getType()) &&
1679 isRedundantTreeWrite(record, previousSnapshot, value, writePod.getOperation())) {
1680 redundantWrites.push_back(writePod.getOperation());
1684 SmallVector<std::shared_ptr<ReferenceNode>> mutatedNodes = {podTree, record};
1686 record, value, writePod.getOperation(), copiedValueTree(value), mutatedNodes
1695 if (isa<CallOp, constrain::ConstraintOpInterface>(op)) {
1700 for (Value operand : op->getOperands()) {
1701 if (requiresAggregateSnapshot(operand.getType())) {
1702 if (
auto valueTree = tryGetValTree(translate(operand))) {
1703 valueTree->clearLastWritesInSubtree();
1708 for (Value operand : op->getOperands()) {
1709 if (requiresAggregateSnapshot(operand.getType())) {
1712 eraseValTree(operand);
1716 state.globals.clear();
1718 state.ramExact.clear();
1719 writeCandidates.clear();
1723 writeCandidates.clear();
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation source
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
void print(llvm::raw_ostream &os) const
::mlir::Region * getCallableRegion()
Required by FunctionOpInterface.
RedundantReadAndWriteEliminationPassBase()
RedundantReadAndWriteEliminationPassBase Base
hash_code hashDynamicAPInt(const DynamicAPInt &value)
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 unsigned getHashValue(const ReferenceID &r)