llvm / llvm/llvm-project

[InstCombine] Known range pessimizes a pointer advance by narrowing it to i8 + mask

Open
#217,314 2 comments 0 reactions 1 assignee Claimed by @Bagodiya View on GitHub
llvm:instcombine missed-optimization
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

Telling clang the true range of a byte makes the code it generates worse. `plain` and `assumed` differ only by a `__builtin_assume` that holds anyway; with it, InstCombine narrows the pointer advance to `i8` and has to mask the result, costing an instruction on x86-64, AArch64 and RISC-V alike. Widening the operand before the arithmetic avoids it. Godbolt: https://godbolt.org/z/GK4Phh8jT

```c
typedef __PTRDIFF_TYPE__ ptrdiff_t;

const signed char *plain(const signed char *p)
{
const signed char op = *p;
return p + (op - 94);
}

const signed char *assumed(const signed char *p)
{
const signed char op = *p;
__builtin_assume(op >= 96);
return p + (op - 94);
}

const signed char *assumed_widened(const signed char *p)
{
const signed char op = *p;
__builtin_assume(op >= 96);
return p + ((ptrdiff_t)op - 94);
}
```

The IR, where `%2` is the loaded `i8`:

```llvm
; plain, assumed_widened
sext i8 %2 to i64 ; resp. zext nneg i8 %2 to i64, then -94 folded into a getelementptr

; assumed
%4 = add nuw i8 %2, 34
%5 = and i8 %4, 63
%6 = zext nneg i8 %5 to i64
```

`op - 94` is `int` arithmetic in the source and the load already materializes the value in a wide register, so narrowing it to `i8` only creates work: the `and` exists solely to make the narrow form correct. Instruction counts, clang 23 (trunk) at `-O2`, same on clang 21:

| | x86-64 | AArch64 | rv64im |
| --- | --- | --- | --- |
| `plain` | 4 | 4 | 4 |
| `assumed` | 5 | 5 | 5 |
| `assumed_widened` | 4 | 4 | 4 |

The same thing happens without `__builtin_assume` when a dominating comparison supplies the range, which is how I ran into it: a loop with an `if (op < 91) { ++p; continue; }` guard ahead of `p += op - 94`. There the masked advance is no longer a constant on the short path, so it also costs the loop its private increment.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.