| //===- TestDenseDataFlowAnalysis.h - Dataflow test utilities ----*- C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "mlir/Analysis/DataFlow/SparseAnalysis.h" | |
| #include "mlir/Analysis/DataFlowFramework.h" | |
| #include "mlir/IR/Value.h" | |
| #include "llvm/ADT/DenseMap.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| #include <optional> | |
| namespace mlir { | |
| namespace dataflow { | |
| namespace test { | |
| /// This lattice represents a single underlying value for an SSA value. | |
| class UnderlyingValue { | |
| public: | |
| /// Create an underlying value state with a known underlying value. | |
| explicit UnderlyingValue(std::optional<Value> underlyingValue = std::nullopt) | |
| : underlyingValue(underlyingValue) {} | |
| /// Whether the state is uninitialized. | |
| bool isUninitialized() const { return !underlyingValue.has_value(); } | |
| /// Returns the underlying value. | |
| Value getUnderlyingValue() const { | |
| assert(!isUninitialized()); | |
| return *underlyingValue; | |
| } | |
| /// Join two underlying values. If there are conflicting underlying values, | |
| /// go to the pessimistic value. | |
| static UnderlyingValue join(const UnderlyingValue &lhs, | |
| const UnderlyingValue &rhs) { | |
| if (lhs.isUninitialized()) | |
| return rhs; | |
| if (rhs.isUninitialized()) | |
| return lhs; | |
| return lhs.underlyingValue == rhs.underlyingValue | |
| ? lhs | |
| : UnderlyingValue(Value{}); | |
| } | |
| /// Compare underlying values. | |
| bool operator==(const UnderlyingValue &rhs) const { | |
| return underlyingValue == rhs.underlyingValue; | |
| } | |
| void print(raw_ostream &os) const { os << underlyingValue; } | |
| private: | |
| std::optional<Value> underlyingValue; | |
| }; | |
| class AdjacentAccess { | |
| public: | |
| using DeterministicSetVector = | |
| SetVector<Operation *, SmallVector<Operation *, 2>, | |
| SmallPtrSet<Operation *, 2>>; | |
| ArrayRef<Operation *> get() const { return accesses.getArrayRef(); } | |
| bool isKnown() const { return !unknown; } | |
| ChangeResult merge(const AdjacentAccess &other) { | |
| if (unknown) | |
| return ChangeResult::NoChange; | |
| if (other.unknown) { | |
| unknown = true; | |
| accesses.clear(); | |
| return ChangeResult::Change; | |
| } | |
| size_t sizeBefore = accesses.size(); | |
| accesses.insert_range(other.accesses); | |
| return accesses.size() == sizeBefore ? ChangeResult::NoChange | |
| : ChangeResult::Change; | |
| } | |
| ChangeResult set(Operation *op) { | |
| if (!unknown && accesses.size() == 1 && *accesses.begin() == op) | |
| return ChangeResult::NoChange; | |
| unknown = false; | |
| accesses.clear(); | |
| accesses.insert(op); | |
| return ChangeResult::Change; | |
| } | |
| ChangeResult setUnknown() { | |
| if (unknown) | |
| return ChangeResult::NoChange; | |
| accesses.clear(); | |
| unknown = true; | |
| return ChangeResult::Change; | |
| } | |
| bool operator==(const AdjacentAccess &other) const { | |
| return unknown == other.unknown && accesses == other.accesses; | |
| } | |
| bool operator!=(const AdjacentAccess &other) const { | |
| return !operator==(other); | |
| } | |
| private: | |
| bool unknown = false; | |
| DeterministicSetVector accesses; | |
| }; | |
| /// This lattice represents, for a given memory resource, the potential last | |
| /// operations that modified the resource. | |
| class AccessLatticeBase { | |
| public: | |
| /// Clear all modifications. | |
| ChangeResult reset() { | |
| if (adjAccesses.empty()) | |
| return ChangeResult::NoChange; | |
| adjAccesses.clear(); | |
| return ChangeResult::Change; | |
| } | |
| /// Join the last modifications. | |
| ChangeResult merge(const AccessLatticeBase &rhs) { | |
| ChangeResult result = ChangeResult::NoChange; | |
| for (const auto &mod : rhs.adjAccesses) { | |
| AdjacentAccess &lhsMod = adjAccesses[mod.first]; | |
| result |= lhsMod.merge(mod.second); | |
| } | |
| return result; | |
| } | |
| /// Set the last modification of a value. | |
| ChangeResult set(Value value, Operation *op) { | |
| AdjacentAccess &lastMod = adjAccesses[value]; | |
| return lastMod.set(op); | |
| } | |
| ChangeResult setKnownToUnknown() { | |
| ChangeResult result = ChangeResult::NoChange; | |
| for (auto &[value, adjacent] : adjAccesses) | |
| result |= adjacent.setUnknown(); | |
| return result; | |
| } | |
| /// Get the adjacent accesses to a value. Returns std::nullopt if they | |
| /// are not known. | |
| const AdjacentAccess *getAdjacentAccess(Value value) const { | |
| auto it = adjAccesses.find(value); | |
| if (it == adjAccesses.end()) | |
| return nullptr; | |
| return &it->getSecond(); | |
| } | |
| void print(raw_ostream &os) const { | |
| for (const auto &lastMod : adjAccesses) { | |
| os << lastMod.first << ":\n"; | |
| if (!lastMod.second.isKnown()) { | |
| os << " <unknown>\n"; | |
| return; | |
| } | |
| for (Operation *op : lastMod.second.get()) | |
| os << " " << *op << "\n"; | |
| } | |
| } | |
| private: | |
| /// The potential adjacent accesses to a memory resource. Use a set vector to | |
| /// keep the results deterministic. | |
| DenseMap<Value, AdjacentAccess> adjAccesses; | |
| }; | |
| /// Define the lattice class explicitly to provide a type ID. | |
| struct UnderlyingValueLattice : public Lattice<UnderlyingValue> { | |
| MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(UnderlyingValueLattice) | |
| using Lattice::Lattice; | |
| }; | |
| /// An analysis that uses forwarding of values along control-flow and callgraph | |
| /// edges to determine single underlying values for block arguments. This | |
| /// analysis exists so that the test analysis and pass can test the behaviour of | |
| /// the dense data-flow analysis on the callgraph. | |
| class UnderlyingValueAnalysis | |
| : public SparseForwardDataFlowAnalysis<UnderlyingValueLattice> { | |
| public: | |
| using SparseForwardDataFlowAnalysis::SparseForwardDataFlowAnalysis; | |
| /// The underlying value of the results of an operation are not known. | |
| LogicalResult | |
| visitOperation(Operation *op, | |
| ArrayRef<const UnderlyingValueLattice *> operands, | |
| ArrayRef<UnderlyingValueLattice *> results) override { | |
| // Hook to test error propagation from visitOperation. | |
| if (op->hasAttr("always_fail")) | |
| return op->emitError("this op is always fails"); | |
| setAllToEntryStates(results); | |
| return success(); | |
| } | |
| /// At an entry point, the underlying value of a value is itself. | |
| void setToEntryState(UnderlyingValueLattice *lattice) override { | |
| propagateIfChanged(lattice, | |
| lattice->join(UnderlyingValue{lattice->getAnchor()})); | |
| } | |
| /// Look for the most underlying value of a value. | |
| static std::optional<Value> | |
| getMostUnderlyingValue(Value value, | |
| function_ref<const UnderlyingValueLattice *(Value)> | |
| getUnderlyingValueFn) { | |
| const UnderlyingValueLattice *underlying; | |
| do { | |
| underlying = getUnderlyingValueFn(value); | |
| if (!underlying || underlying->getValue().isUninitialized()) | |
| return std::nullopt; | |
| Value underlyingValue = underlying->getValue().getUnderlyingValue(); | |
| if (underlyingValue == value) | |
| break; | |
| value = underlyingValue; | |
| } while (true); | |
| return value; | |
| } | |
| }; | |
| } // namespace test | |
| } // namespace dataflow | |
| } // namespace mlir |