[InstCombine] Fold strlen over selected constant strings
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Fold `strlen` of `phi`/`select` over constant string literals
### Summary
LLVM leaves a runtime `strlen(selected_ptr)` call when `selected_ptr` is a `phi` or nested `select` over constant string literals, even though every leaf has a known compile-time length. The result length can be computed as a `phi`/`select` of constants instead of calling `strlen` at runtime.
A top-level 2-way select is already folded (`strlen(x ? "foo" : "bars")` in `llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp`), but the same shape over a `phi`, a nested select, or a select-of-phi is not.
### Reduced reproducer
```llvm
target triple = "arm64-apple-darwin24.3.0"
@s0 = private unnamed_addr constant [3 x i8] c"ok\00", align 1
@s1 = private unnamed_addr constant [8 x i8] c"missing\00", align 1
@s2 = private unnamed_addr constant [10 x i8] c"cancelled\00", align 1
@s3 = private unnamed_addr constant [11 x i8] c"timed out!\00", align 1
declare i64 @strlen(ptr)
define i64 @f(i32 %x) {
entry:
%idx = and i32 %x, 3
switch i32 %idx, label %case0 [
i32 1, label %case1
i32 2, label %case2
i32 3, label %case3
]
case0: br label %join
case1: br label %join
case2: br label %join
case3: br label %join
join:
%selected = phi ptr [ @s0, %case0 ], [ @s1, %case1 ], [ @s2, %case2 ], [ @s3, %case3 ]
%len = call i64 @strlen(ptr %selected)
ret i64 %len
}
```
Run: `opt -S -passes=instcombine repro.ll`
### Actual result
The `strlen` call survives:
```llvm
join:
%selected = phi ptr [ @s0, %case0 ], [ @s1, %case1 ], [ @s2, %case2 ], [ @s3, %case3 ]
%len = call i64 @strlen(ptr nonnull dereferenceable(1) %selected)
ret i64 %len
```
### Expected result
Fold to a `phi` of constant lengths:
```llvm
join:
%len = phi i64 [ 2, %case0 ], [ 7, %case1 ], [ 9, %case2 ], [ 10, %case3 ]
ret i64 %len
```
Under `default` the resulting length `phi` is then converted by SimplifyCFG into a constant length lookup table, giving a load of a constant `[2, 7, 9, 10]` table instead of a `strlen` call.
### Why this differs from the closed issue #202573
A related but distinct shape was filed in #202573 (loaded pointer from a constant string table, feeding `strncmp`). That issue was closed because the motivating example was not on a hot path. This report is a different shape and has broader evidence:
- Domain is `phi` / nested `select` / select-of-phi over constant literals (not a load through a table pointer), feeding inlined `std::string` construction/copy logic.
- A scan over optimized IR found the surviving `strlen(selected_ptr)` shape in **80 hits across 54 files and 14 projects** (breakdown: phi 19, phi_select 44, nested_select 17).
- Native timing smoke on arm64 Darwin (matching checksums): baseline 11.4 ns/iter vs. selected-length form 7.6 ns/iter (~1.5x) for the copy path, i.e. removing the repeated `strlen` call.
### Notes
- The transform is speculatable: every leaf is a constant string with a known first-NUL length, so each arm folds to a pure constant.
- Non-constant / loaded / mutable pointer domains, and interior pointers with unproven offsets, must be rejected.
- I would be happy to implement this in `SimplifyLibCalls.cpp` (`optimizeStringLength`) and submit a patch.
Contributor guide
Assessment
This issue has not been assessed yet.