[llvm] [InstSimplify] Fold trunc to constant when all result bits are known (PR #212123)
Timur Golubovich via llvm-commits
llvm-commits at lists.llvm.org
Tue Jul 28 17:56:31 PDT 2026
timurgol007 wrote:
Hi @nikic, thanks for the review!
You're right that demanded bits in InstCombine handles the single-use phi case. The motivation here is the **multi-use phi** case that demanded bits does not cover.
Consider the libc++ `basic_string` short-string optimization pattern (reduced test in `trunc-multiuse-phi.ll`):
```llvm
merge:
%hdr = phi i64 [ %hdr.short, %short ], [ %hdr.long, %long ]
%data = phi ptr [ %buf, %short ], [ %heap, %long ]
%is.heap = trunc i64 %hdr to i1
br i1 %is.heap, label %do.free, label %done
do.free:
%cap.mask = and i64 %hdr, -2
call void @_ZdlPvm(ptr %data, i64 %cap.mask)
...
```
The phi `%hdr` has **two uses**: the `trunc to i1` and the `and i64 %hdr, -2`. Because the phi has multiple uses:
1. `SimplifyDemandedInstructionBits` cannot fold the trunc, it only simplifies the trunc's demanded bits, but can't eliminate it when the phi has other users that demand different bits.
2. `foldOpIntoPhi` (without `AllowMultipleUses`) bails out at the single-use check (InstructionCombining.cpp:1946).
3. `canEvaluateTruncated` fails because `allPendingVisited()` rejects the multi-use phi (the `and` user is not part of the trunc expression tree).
So without this patch, `trunc i64 %hdr to i1` survives the full `-O2` pipeline even though:
- On the short path: `%hdr.short = shl nuw nsw i64 %len, 1` -> bit 0 = 0 -> trunc = false
- On the long path: `%hdr.long = or disjoint i64 %cap, 1` -> bit 0 = 1 -> trunc = true
The trunc is semantically just `!%is.short` (the branch condition), but no existing fold recovers this. SimplifyCFG's `foldCondBranchOnValueKnownInPredecessorImpl` can't see through the `trunc i64 %phi to i1` either it only handles direct phi-of-constants or exact branch conditions as the condition operand.
You can reproduce the unoptimized case on trunk with:
```
opt -O2 -S trunc-multiuse-phi.ll
```
The `trunc i64 %hdr to i1` and the conditional `do.free` block survive unchanged through the full pipeline.
The approach in the second commit: when `foldOpIntoPhi` is about to process a cast on a multi-use phi, we first check if `simplifyCastInst` can fold each incoming value to a constant (using the incoming block's terminator as context). If all simplify, we pass `AllowMultipleUses=true` so `foldOpIntoPhi` proceeds and it will then simplify each incoming trunc to a constant and produce a `phi i1 [false, true]`, which subsequent folds reduce to the branch condition.
The first commit (`simplifyCastInst` KnownBits fold) provides the per-incoming-value simplification that makes this work as `simplifyCastInst` with the incoming block context sees that each value's low bits are fully determined and folds the trunc to a constant.
https://github.com/llvm/llvm-project/pull/212123
More information about the llvm-commits
mailing list