llvm / llvm/llvm-project

[RISCV] `icmp samesign` on i8/i16 emits a redundant zero-extension

Open
#217,273 1 comment 0 reactions 1 assignee Claimed by @topperc View on GitHub
backend:RISC-V missed-optimization
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

On rv64, when a dominating signed compare lets a second comparison be canonicalized to `icmp samesign`, the backend lowers that comparison as an unsigned compare and materializes a zero-extension of the narrow operand — `zext.b` for `i8`, an `slli`/`srli` pair for `i16` — even though `samesign` records that the signed and unsigned forms are equivalent here and the operand is already available sign-extended in a register from the `lb`/`lh` (the pointer arithmetic on the same value uses it in that form); GCC instead compares the loaded value directly and emits no extension, and clang itself already gets this right at `i32`, where the compare is lowered against the `lw` result with nothing extra. Godbolt: https://godbolt.org/z/5KP419zPb

```c
const signed char *f8(const signed char *p)
{
const signed char op = *p;
if (op < 91) return p + 1;
if (op >= 96) return p + op - 94;
return p + 2;
}
```

`clang --target=riscv64 -march=rv64im -mabi=lp64 -O2`:

```asm
f8:
lb a1, 0(a0)
li a2, 90
blt a2, a1, .LBB0_2
addi a0, a0, 1
ret
.LBB0_2:
zext.b a2, a1 # redundant
li a3, 96
bltu a2, a3, .LBB0_4
add a0, a0, a1 # uses the sign-extended value directly
addi a0, a0, -94
ret
.LBB0_4:
addi a0, a0, 2
ret
```

`riscv64-none-elf-gcc 15.2 -march=rv64im -mabi=lp64 -O2`:

```asm
f8:
lb a5,0(a0)
li a3,90
mv a4,a0
ble a5,a3,.L6
li a3,95
addi a0,a0,2
bgt a5,a3,.L7 # signed compare, no extension
ret
.L7:
addi a5,a5,-94
add a0,a4,a5
ret
.L6:
addi a0,a0,1
ret
```

The extension is the only difference; `li a3, 96` + `blt a1, a3, .LBB0_4` would do.

Relevant optimized IR — note the pointer arithmetic is a plain `sext`, so the comparison is the only thing asking for the zero-extended form:

```llvm
%2 = load i8, ptr %0, align 1
%3 = sext i8 %2 to i64
%4 = icmp slt i8 %2, 91
br i1 %4, label %5, label %7
7:
%8 = icmp samesign ugt i8 %2, 95
br i1 %8, label %9, label %12
9:
%10 = getelementptr inbounds nuw i8, ptr %0, i64 %3
%11 = getelementptr inbounds i8, ptr %10, i64 -94
```

The same shape with `int` produces no extension at all (`bltu` straight on the `lw` result), so only the sub-word widths are affected. Reproduces with clang 21 and clang 23 (trunk), on both `rv64im` and `rv64gc`. x86-64 and AArch64 are unaffected.

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.