Skip to content

Commit aa97bec

Browse files
committed
Add AMDGPU m019 high-bit xor miscompile
1 parent 899b16b commit aa97bec

4 files changed

Lines changed: 141 additions & 1 deletion

File tree

‎amdgpu/README.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ are local artifacts and are ignored by git; set `FUZZX_RUNTIME_ROOT`,
8181
`CORPUS_ROOT`, `LOG_DIR`, `ARTIFACT_ROOT`, or `FUZZX_FINDINGS_DIR` to override
8282
the default local runtime paths.
8383

84+
### Known-Bug Suppression
85+
86+
Known bug patterns are suppressed by default so continued fuzzing does not keep
87+
rediscovering the same issue.
88+
89+
| Flag | Default | Meaning |
90+
| --- | --- | --- |
91+
| `FUZZX_ALLOW_M019_HIGHBIT_OR_XOR=1` | unset | Re-enable the outer high-bit `(x | C) ^ x` shape for [m019](known-miscompiles/m019-highbit-or-xor/NOTES.md). |
92+
8493
## Layout
8594

8695
| Path | Purpose |
@@ -131,6 +140,7 @@ Tested toolchains as of 2026-05-16:
131140
| [m016-scalar-fshl-one](known-miscompiles/m016-scalar-fshl-one/NOTES.md) | ✅ | ❌ | ❌ | `-O0` lowers scalar `fshl.i32(x, y, 1)` through a 64-bit shift-by-`-1` sequence that returns only bit 31. |
132141
| [m017-vector-and-lane0-clear-xor](known-miscompiles/m017-vector-and-lane0-clear-xor/NOTES.md) | ❌ | ✅ | ✅ | ROCm 7.2.3 `-O0` drops a vector lane-0 `and`/`extractelement` clear before `xor`; LLVM HEAD and ROCm HEAD already pass. |
133142
| [m018-two-private-memory-ops](known-miscompiles/m018-two-private-memory-ops/NOTES.md) | ❌ | ✅ | ✅ | ROCm 7.2.3 `-O0` intermittently reads stale scratch data across two private-memory sequences; LLVM HEAD and ROCm HEAD pass 50 repeated combined runs. |
143+
| [m019-highbit-or-xor](known-miscompiles/m019-highbit-or-xor/NOTES.md) | ❌ | ❌ | ❌ | `-O0` combines a high-bit `(x | C) ^ x` expression into `v_bitop3_b32` with the wrong truth table or operands. |
134144

135145
*Human-written note:* Up through bug m016 I was testing against upstream LLVM. But then it became clear that the ROCm 7.2.3 release doesn't have most of the bugs that are appearing in upstream. I'm more interested in bugs that appear in the release, so after this, I started testing against 7.2.3 (built from source).
136146

‎amdgpu/fuzzer/llvm_amdgpu_diff_fuzzer.cpp‎

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ uint64_t randomInteresting64(std::minstd_rand &Gen) {
9999
}
100100
}
101101

102+
bool envFlag(const char *Name, bool Default) {
103+
const char *Value = std::getenv(Name);
104+
if (!Value || !*Value)
105+
return Default;
106+
return std::strcmp(Value, "0") != 0 && std::strcmp(Value, "false") != 0 &&
107+
std::strcmp(Value, "False") != 0 && std::strcmp(Value, "no") != 0 &&
108+
std::strcmp(Value, "off") != 0;
109+
}
110+
102111
std::array<uint32_t, InputCount> makeInputs(const uint8_t *Data, size_t Size) {
103112
std::array<uint32_t, InputCount> Inputs{};
104113
std::array<uint32_t, 8> Edges = {0, 1, 0x7fffffffu,
@@ -454,7 +463,33 @@ void scrubPoisonAnnotations(Module &M) {
454463
}
455464
}
456465

466+
bool isHighBitI32Constant(const Value *V) {
467+
const auto *CI = dyn_cast<ConstantInt>(V);
468+
return CI && CI->getType()->isIntegerTy(32) &&
469+
(CI->getZExtValue() & 0x80000000ull) != 0;
470+
}
471+
472+
bool isOrWithHighBitConstantOf(const Value *MaybeOr, const Value *Other) {
473+
const auto *BO = dyn_cast<BinaryOperator>(MaybeOr);
474+
if (!BO || BO->getOpcode() != Instruction::Or)
475+
return false;
476+
return (BO->getOperand(0) == Other &&
477+
isHighBitI32Constant(BO->getOperand(1))) ||
478+
(BO->getOperand(1) == Other &&
479+
isHighBitI32Constant(BO->getOperand(0)));
480+
}
481+
482+
bool triggersM019HighBitOrXor(const Instruction &I) {
483+
const auto *BO = dyn_cast<BinaryOperator>(&I);
484+
if (!BO || BO->getOpcode() != Instruction::Xor ||
485+
!BO->getType()->isIntegerTy(32))
486+
return false;
487+
return isOrWithHighBitConstantOf(BO->getOperand(0), BO->getOperand(1)) ||
488+
isOrWithHighBitConstantOf(BO->getOperand(1), BO->getOperand(0));
489+
}
490+
457491
bool validateIRCorpusModule(Module &M) {
492+
bool AllowM019 = envFlag("FUZZX_ALLOW_M019_HIGHBIT_OR_XOR", false);
458493
Function *Kernel = findIRKernel(M);
459494
if (!Kernel)
460495
return false;
@@ -467,7 +502,8 @@ bool validateIRCorpusModule(Module &M) {
467502
return false;
468503
for (BasicBlock &BB : F)
469504
for (Instruction &I : BB)
470-
if (!isAllowedIRInstruction(I))
505+
if (!isAllowedIRInstruction(I) ||
506+
(!AllowM019 && triggersM019HighBitOrXor(I)))
471507
return false;
472508
continue;
473509
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# m019: high-bit `(x | C) ^ x` is miscompiled through `v_bitop3_b32`
2+
3+
Found while fuzzing the ROCm 7.2.3 source build with the LLVM-bitcode C++
4+
fuzzer.
5+
6+
```bash
7+
known-miscompiles/run_ll_reproducer.sh \
8+
known-miscompiles/m019-highbit-or-xor/reduced.ll
9+
```
10+
11+
Observed result:
12+
13+
```text
14+
input=0x00000000
15+
O0=0x80000000
16+
O2=0x00000000
17+
mismatch=true
18+
```
19+
20+
## Reduction
21+
22+
The reduced IR computes:
23+
24+
```llvm
25+
%salt = xor i32 %workitem.id.x, 0x80000000
26+
%mix = xor i32 %load, %salt
27+
%and = and i32 %mix, %salt
28+
%or = or i32 %and, 0x80000000
29+
%xor = xor i32 %or, %and
30+
```
31+
32+
For the single executed work-item and input `0`, `%salt` and `%and` are both
33+
`0x80000000`, so `%xor` should be zero.
34+
35+
## Root Cause Notes
36+
37+
At `-O2`, the expression is simplified to `load & 0x80000000`, which returns
38+
zero for the reproducer input.
39+
40+
At `-O0`, instruction selection combines the bit expression into:
41+
42+
```asm
43+
v_bitop3_b32 v2, v2, v3, s2 bitop3:0x62
44+
```
45+
46+
where `v2` is the loaded input, `v3` is the work-item id, and `s2` is
47+
`0x80000000`. For work-item zero and input zero this instruction produces
48+
`0x80000000`, but the LLVM IR expression is defined and evaluates to zero.
49+
50+
## Toolchain Results
51+
52+
| Toolchain | Result |
53+
| --- | --- |
54+
| ROCm 7.2.3 source build from tag `rocm-7.2.3`, commit `f58b06dce1f9c15707c5f808fd002e18c2accf7e`, `Release`, sanitizer coverage, no ASan | Reproduces. |
55+
| LLVM HEAD, commit `10756d32f96154f0889eda159ea9a26bc4188bda` | Reproduces. |
56+
| ROCm HEAD, commit `9115c466b3577830455f70c4f492429bf6c64b25` | Reproduces. |
57+
58+
## Fuzzer Follow-Up
59+
60+
The IR-bitcode fuzzer now suppresses the outer high-bit `(x | C) ^ x` idiom by
61+
default. Set `FUZZX_ALLOW_M019_HIGHBIT_OR_XOR=1` to re-enable this shape.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
; RUN-INPUTS: 0x0
2+
; RUN-COMBINED: 1
3+
; RUN-LLVM-BUILD: build/rocm-7.2.3-llvm-cov-release
4+
5+
target datalayout = "e-p:64:64-p1:64:64-p2:32:32-p3:32:32-p4:64:64-p5:32:32-p6:32:32-p7:160:256:256:32-p8:128:128:128:48-p9:192:256:256:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-v2048:2048-n32:64-S32-A5-G1-ni:7:8:9"
6+
target triple = "amdgcn-amd-amdhsa"
7+
8+
define protected amdgpu_kernel void @fuzz_kernel(ptr addrspace(1) %in, ptr addrspace(1) %out, i32 %n) #0 {
9+
entry:
10+
%wi = call i32 @llvm.amdgcn.workitem.id.x()
11+
%ok = icmp ult i32 %wi, %n
12+
br i1 %ok, label %body, label %exit
13+
14+
body:
15+
%idx64 = zext i32 %wi to i64
16+
%in.ptr = getelementptr i32, ptr addrspace(1) %in, i64 %idx64
17+
%v = load i32, ptr addrspace(1) %in.ptr, align 4
18+
%salt = xor i32 %wi, -2147483648
19+
%mix = xor i32 %v, %salt
20+
%and = and i32 %mix, %salt
21+
%or = or i32 %and, -2147483648
22+
%xor = xor i32 %or, %and
23+
%out.ptr = getelementptr i32, ptr addrspace(1) %out, i64 %idx64
24+
store i32 %xor, ptr addrspace(1) %out.ptr, align 4
25+
br label %exit
26+
27+
exit:
28+
ret void
29+
}
30+
31+
declare i32 @llvm.amdgcn.workitem.id.x()
32+
33+
attributes #0 = { convergent nounwind "amdgpu-flat-work-group-size"="1,256" "target-cpu"="gfx950" "uniform-work-group-size"="true" }

0 commit comments

Comments
 (0)