================ @@ -63,80 +113,140 @@ static OpBuilder::InsertPoint computeInsertPoint(Value value) { return OpBuilder::InsertPoint(insertBlock, insertPt); } +/// Helper function that computes an insertion point where the given values are +/// defined and can be used without a dominance violation. +static OpBuilder::InsertPoint computeInsertPoint(ArrayRef<Value> vals) { + assert(!vals.empty() && "expected at least one value"); + OpBuilder::InsertPoint pt = computeInsertPoint(vals.front()); + for (Value v : vals.drop_front()) + pt = chooseLaterInsertPoint(pt, computeInsertPoint(v)); + return pt; +} + //===----------------------------------------------------------------------===// // ConversionValueMapping //===----------------------------------------------------------------------===// +/// A vector of SSA values, optimized for the most common case of a single +/// value. +using ValueVector = SmallVector<Value, 1>; + namespace { + +/// Helper class to make it possible to use `ValueVector` as a key in DenseMap. +struct ValueVectorMapInfo { + static ValueVector getEmptyKey() { return ValueVector{}; } + static ValueVector getTombstoneKey() { return ValueVector{}; } + static ::llvm::hash_code getHashValue(ValueVector val) { + return ::llvm::hash_combine_range(val.begin(), val.end()); + } + static bool isEqual(ValueVector LHS, ValueVector RHS) { return LHS == RHS; } +}; + /// This class wraps a IRMapping to provide recursive lookup /// functionality, i.e. we will traverse if the mapped value also has a mapping. struct ConversionValueMapping { /// Return "true" if an SSA value is mapped to the given value. May return /// false positives. bool isMappedTo(Value value) const { return mappedTo.contains(value); } - /// Lookup the most recently mapped value with the desired type in the + /// Lookup the most recently mapped values with the desired types in the /// mapping. /// /// Special cases: - /// - If the desired type is "null", simply return the most recently mapped - /// value. - /// - If there is no mapping to the desired type, also return the most - /// recently mapped value. - /// - If there is no mapping for the given value at all, return the given - /// value. - Value lookupOrDefault(Value from, Type desiredType = nullptr) const; - - /// Lookup a mapped value within the map, or return null if a mapping does not - /// exist. If a mapping exists, this follows the same behavior of - /// `lookupOrDefault`. - Value lookupOrNull(Value from, Type desiredType = nullptr) const; + /// - If the desired type range is empty, simply return the most recently + /// mapped values. + /// - If there is no mapping to the desired types, also return the most + /// recently mapped values. + /// - If there is no mapping for the given values at all, return the given + /// values. + ValueVector lookupOrDefault(ValueVector from, + TypeRange desiredTypes = {}) const; + + /// Lookup the given values within the map, or return an empty vector if the + /// values are not mapped. If they are mapped, this follows the same behavior + /// as `lookupOrDefault`. + ValueVector lookupOrNull(const ValueVector &from, + TypeRange desiredTypes = {}) const; /// Map a value to the one provided. - void map(Value oldVal, Value newVal) { + void map(const ValueVector &oldVal, const ValueVector &newVal) { LLVM_DEBUG({ - for (Value it = newVal; it; it = mapping.lookupOrNull(it)) - assert(it != oldVal && "inserting cyclic mapping"); + ValueVector next = newVal; + while (true) { + assert(next != oldVal && "inserting cyclic mapping"); + auto it = mapping.find(next); + if (it == mapping.end()) + break; + next = it->second; + } }); - mapping.map(oldVal, newVal); - mappedTo.insert(newVal); + mapping[oldVal] = newVal; + for (Value v : newVal) + mappedTo.insert(v); } - /// Drop the last mapping for the given value. - void erase(Value value) { mapping.erase(value); } + /// Drop the last mapping for the given values. + void erase(ValueVector value) { mapping.erase(value); } private: /// Current value mappings. - IRMapping mapping; + DenseMap<ValueVector, ValueVector, ValueVectorMapInfo> mapping; /// All SSA values that are mapped to. May contain false positives. DenseSet<Value> mappedTo; }; } // namespace -Value ConversionValueMapping::lookupOrDefault(Value from, - Type desiredType) const { - // Try to find the deepest value that has the desired type. If there is no - // such value, simply return the deepest value. - Value desiredValue; +ValueVector +ConversionValueMapping::lookupOrDefault(ValueVector from, + TypeRange desiredTypes) const { + // Try to find the deepest values that have the desired types. If there is no + // such mapping, simply return the deepest values. + ValueVector desiredValue; do { - if (!desiredType || from.getType() == desiredType) + // Store the current value if the types match. + if (desiredTypes.empty() || TypeRange(from) == desiredTypes) desiredValue = from; - Value mappedValue = mapping.lookupOrNull(from); - if (!mappedValue) + // If possible, Replace each value with (one or multiple) mapped values. + ValueVector next; ---------------- matthias-springer wrote:
I struggled with this function quite a lot. There are few details that are tricky. > To me this means we have no longer a distinct way to get the "most-recently" > mapped value. That's correct. > That said, in practice the latter case may only succeed if there was a N:M > materialization right? Correct. > Is my impression correct that the second case can therefore just be seen as > an optimization to reuse a materialization rather than creating a new one > (for when the desired types match). Yes. If we miss a materialization, it's not a problem for a functional point of view. (We will just create another one.) But missing an actual replacement could be a problem. That's why the implementation just looks up values individually. That way it is guaranteed that we find all replacements. > Regarding the first case: What confuses me a bit is the append range > behaviour. Playing with asserts I found that in the test suite it->second and > from are never larger than 1 at the same time. Can this happen in theory? Could I e.g. have one pattern replacing a tuple<i64, i64> with two i64s and those replaced by another pattern with two i32s? And any subsequent operation (or target materialization for the operation) using the tuple<i64, i64> would receive the four i32s? Yes, that's what I had in mind. It looks like we have no test case that exercises this... There was another design that I played with: having two separate mapping for replacements and materializations: ```c++ DenseMap<Value, ValueRange> replacements; // `materializations` can store multiple materializations to different types. DenseMap<SmallVector<Value>, SmallVector<ValueRange>> materializations; ``` My initial lookup mechanism worked as follows: 1. Find the most recently mapped values in `replacements`, regardless of the type. Let's call them `repl`. 2. Try to find a materialization to the correct types. This approach avoids the problem of multiple possible lookup paths. But there is a problem: let's say a pattern P1 without a type converter replaces SSA value A with B, but A and B have the same type. Then another pattern P2 with a type converter (and a registered materialization callback) replaces B with C, and B and C have different types. If we were to lookup the most recently mapped value of A immediately, the conversion driver would try to build a materialization from type(A) to type(C) with P1's type converter (which will fail). After a few more experiments, I decided to stay as close as possible to the current `ConversionValueMapping` implementation. In particular, not splitting the `IRMapping` into multiple things. https://github.com/llvm/llvm-project/pull/116524 _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits