[llvm] [InstCombine] Support multi-use values in cast elimination transforms (PR #165877)
Yingwei Zheng via llvm-commits
llvm-commits at lists.llvm.org
Sat Nov 1 00:57:09 PDT 2025
================
@@ -227,9 +262,175 @@ Instruction *InstCombinerImpl::commonCastTransforms(CastInst &CI) {
return nullptr;
}
+namespace {
+
+/// Helper class for evaluating whether a value can be computed in a different
+/// type without changing its value. Used by cast simplification transforms.
+class TypeEvaluationHelper {
+public:
+ /// Return true if we can evaluate the specified expression tree as type Ty
+ /// instead of its larger type, and arrive with the same value.
+ /// This is used by code that tries to eliminate truncates.
+ [[nodiscard]] static bool canEvaluateTruncated(Value *V, Type *Ty,
+ InstCombinerImpl &IC,
+ Instruction *CxtI);
+
+ /// Determine if the specified value can be computed in the specified wider
+ /// type and produce the same low bits. If not, return false.
+ [[nodiscard]] static bool canEvaluateZExtd(Value *V, Type *Ty,
+ unsigned &BitsToClear,
+ InstCombinerImpl &IC,
+ Instruction *CxtI);
+
+ /// Return true if we can take the specified value and return it as type Ty
+ /// without inserting any new casts and without changing the value of the
+ /// common low bits.
+ [[nodiscard]] static bool canEvaluateSExtd(Value *V, Type *Ty);
+
+private:
+ /// Constants and extensions/truncates from the destination type are always
+ /// free to be evaluated in that type.
+ [[nodiscard]] static bool canAlwaysEvaluateInType(Value *V, Type *Ty);
+
+ /// Check if we traversed all the users of the multi-use values we've seen.
+ [[nodiscard]] bool allPendingVisited() const {
+ return llvm::all_of(Pending,
+ [this](Value *V) { return Visited.contains(V); });
+ }
+
+ /// A generic wrapper for canEvaluate* recursions to inject visitation
+ /// tracking and enforce correct multi-use value evaluations.
+ [[nodiscard]] bool
+ canEvaluate(Value *V, Type *Ty,
+ llvm::function_ref<bool(Value *, Type *Type)> Pred) {
+ if (canAlwaysEvaluateInType(V, Ty))
+ return true;
+
+ if (!isa<Instruction>(V))
+ return false;
+
+ auto *I = cast<Instruction>(V);
+ // We insert false by default to return false when we encounter user loops.
+ const auto [It, Inserted] = Visited.insert({V, false});
+
+ // There are three possible cases for us having information on this value
+ // in the Visited map:
+ // 1. We properly checked it and concluded that we can evaluate it (true)
+ // 2. We properly checked it and concluded that we can't (false)
+ // 3. We started to check it, but during the recursive traversal we came
+ // back to it.
+ //
+ // For cases 1 and 2, we can safely return the stored result. For case 3, we
+ // can potentially have a situation where we can evaluate recursive user
+ // chains, but that can be quite tricky to do properly and isntead, we
+ // return false.
+ //
+ // In any case, we should return whatever was there in the map to begin
+ // with.
+ if (!Inserted)
+ return It->getSecond();
+
+ // We can easily make a decision about single-user values whether they can
+ // be evaluated in a different type or not, we came from that user. This is
+ // not as simple for multi-user values.
+ //
+ // In general, we have the following case (inverted control-flow, users are
+ // at the top):
+ //
+ // Cast %A
+ // ____|
+ // /
+ // %A = Use %B, %C
+ // ________| |
+ // / |
+ // %B = Use %D |
+ // ________| |
+ // / |
+ // %D = Use %C |
+ // ________|___|
+ // /
+ // %C = ...
+ //
+ // In this case, when we check %A, %B and %C, we are confident that we can
+ // make the decision here and now, since we came from their only users.
+ //
+ // For %C, it is harder. We come there twice, and when we come the first
+ // time, it's hard to tell if we will visit the second user (technically
+ // it's not hard, but we might need a lot of repetitive checks with non-zero
+ // cost).
+ //
+ // In the case above, we are allowed to evaluate %C in different type
+ // because all of it users were part of the traversal.
+ //
+ // In the following case, however, we can't make this conclusion:
+ //
+ // Cast %A
+ // ____|
+ // /
+ // %A = Use %B, %C
+ // ________| |
+ // / |
+ // %B = Use %D |
+ // ________| |
+ // / |
+ // %D = Use %C |
+ // | |
+ // foo(%C) | | <- never traversing foo(%C)
+ // ________|___|
+ // /
+ // %C = ...
+ //
+ // In this case, we still can evaluate %C in a different type, but we'd need
+ // to create a copy of the original %C to be used in foo(%C). Such
+ // duplication might be not profitable.
+ //
+ // For this reason, we collect all users of the mult-user values and mark
+ // them as "pending" and defer this decision to the very end. When we are
+ // done and and ready to have a positive verdict, we should double-check all
+ // of the pending users and ensure that we visited them. allPendingVisited
+ // predicate checks exactly that.
+ if (!I->hasOneUse()) {
+ llvm::transform(I->uses(), std::back_inserter(Pending),
+ [](Use &U) { return U.getUser(); });
+ }
----------------
dtcxzyw wrote:
```suggestion
if (!I->hasOneUse())
llvm::append_range(Pending, I->users());
```
https://github.com/llvm/llvm-project/pull/165877
More information about the llvm-commits
mailing list