Skip to content

Suspected miscompilation with bool as u32 #158206

Description

@qti3e

Godbolt link: https://godbolt.org/z/G4hxdn4Wa

I tried this code (simplified to a ~minimal version):

struct Store {
    ring: [Kind; 32],
}

#[derive(Copy, Clone, Eq, PartialEq)]
enum Kind {
    A,
    B,
    C
}

struct Ring(u32);

impl Ring {
    fn peek(&self, store: &Store) -> Kind {
        store.ring[(self.0 as usize) % 32]
    }

    pub fn peek_test(&self, store: &Store, expected: Kind) -> bool {
        self.peek(store) == expected
    }

    #[inline]
    pub fn consume_test(&mut self, store: &Store, expected: Kind) -> bool {
        let x = self.peek_test(store, expected);
        self.0 += if x { 1 } else { 0 };
        x
    }

    // Uses a u32 cast instead of the 
    #[inline]
    pub fn consume_test_borked(&mut self, store: &Store, expected: Kind) -> bool {
        let x = self.peek_test(store, expected);
        self.0 += x as u32;
        x
    }
}

#[unsafe(no_mangle)]
fn foo(store: &Store, mut lex: Ring) -> usize {
    if !lex.consume_test(store, Kind::A) {
        return 0;
    }
    match lex.peek(store) {
        Kind::A => 17,
        _ => 27,
    }
}

#[unsafe(no_mangle)]
fn foo_borked(store: &Store, mut lex: Ring) -> usize {
    if !lex.consume_test_borked(store, Kind::A) {
        return 0;
    }
    match lex.peek(store) {
        Kind::A => 17,
        _ => 27,
    }
}

I expected foo and foo_borked to have identical behavior/codegen.

Instead, this happened: foo_borked compiled into a function that always takes the Kind::A branch, never returning 27.

; rustc -Copt-level=3 [also =2]

foo:
        mov     eax, esi
        and     eax, 31
        cmp     byte ptr [rdi + rax], 0
        je      .LBB0_2
        xor     eax, eax
        ret
.LBB0_2:
        inc     esi
        and     esi, 31
        cmp     byte ptr [rdi + rsi], 0
        mov     ecx, 17
        mov     eax, 27
        cmove   rax, rcx
        ret

foo_borked:
        and     esi, 31
        xor     ecx, ecx
        cmp     byte ptr [rdi + rsi], 0
        mov     eax, 17
        cmovne  rax, rcx
        ret

Looking at the LLVM IR with -Cno-prepopulate-passes it appears that this is a rustc bug, and given that -Zmir-opt-level=0 produces valid code (foo_borked = foo) this is likely a Mir opt issue.

define noundef i64 @foo_borked(ptr noalias nofree noundef readonly captures(address, read_provenance) dereferenceable(32) %store, i32 noundef %0) unnamed_addr {
start:
  %__self_discr.dbg.spill = alloca [8 x i8], align 8
  %__arg1_discr.dbg.spill = alloca [8 x i8], align 8
  %expected.dbg.spill1 = alloca [1 x i8], align 1
  %expected.dbg.spill = alloca [1 x i8], align 1
  %store.dbg.spill = alloca [8 x i8], align 8
  %x = alloca [1 x i8], align 1
  %_0 = alloca [8 x i8], align 8
  %lex = alloca [4 x i8], align 4
  store i32 %0, ptr %lex, align 4
  store ptr %store, ptr %store.dbg.spill, align 8
  store i8 0, ptr %expected.dbg.spill, align 1
  store i8 0, ptr %expected.dbg.spill1, align 1
  store i64 0, ptr %__arg1_discr.dbg.spill, align 8
  call void @llvm.lifetime.start.p0(ptr %x)
  %_14 = load i32, ptr %lex, align 4
  %_13 = zext i32 %_14 to i64
  %_12 = urem i64 %_13, 32
  %_15 = icmp ult i64 %_12, 32
  br i1 %_15, label %bb7, label %panic

bb7:
  %1 = getelementptr inbounds nuw i8, ptr %store, i64 %_12
  %_10 = load i8, ptr %1, align 1
  %__self_discr = zext i8 %_10 to i64
  store i64 %__self_discr, ptr %__self_discr.dbg.spill, align 8
  ; %x is being loaded here without a prior store, becoming a poison
  %2 = load i8, ptr %x, align 1
  %3 = trunc nuw i8 %2 to i1
  %4 = icmp ule i1 %3, true
  call void @llvm.assume(i1 %4)
  %_8 = zext i1 %3 to i32
  %5 = load i32, ptr %lex, align 4
  %6 = add i32 %5, %_8
  store i32 %6, ptr %lex, align 4
  %7 = icmp eq i64 %__self_discr, 0
  br i1 %7, label %bb1, label %bb2

panic:
  call void @core[dcb47395e1cc1d61]::panicking::panic_bounds_check(i64 noundef %_12, i64 noundef 32, ptr noalias nofree noundef readonly align 8 captures(address, read_provenance) dereferenceable(24) @alloc_7414d1c37f862b99e65b1576388dc6f7) #4
  unreachable

bb1:
  call void @llvm.lifetime.end.p0(ptr %x)
  %_19 = load i32, ptr %lex, align 4
  %_18 = zext i32 %_19 to i64
  %_17 = urem i64 %_18, 32
  %_20 = icmp ult i64 %_17, 32
  br i1 %_20, label %bb8, label %panic2

bb2:
  store i64 0, ptr %_0, align 8
  call void @llvm.lifetime.end.p0(ptr %x)
  br label %bb6

bb8:
  %8 = getelementptr inbounds nuw i8, ptr %store, i64 %_17
  %_5 = load i8, ptr %8, align 1
  %_7 = zext i8 %_5 to i64
  %9 = icmp eq i64 %_7, 0
  br i1 %9, label %bb4, label %bb3

panic2:
  call void @core[dcb47395e1cc1d61]::panicking::panic_bounds_check(i64 noundef %_17, i64 noundef 32, ptr noalias nofree noundef readonly align 8 captures(address, read_provenance) dereferenceable(24) @alloc_7414d1c37f862b99e65b1576388dc6f7) #4
  unreachable

bb4:
  store i64 17, ptr %_0, align 8
  br label %bb5

bb3:
  store i64 27, ptr %_0, align 8
  br label %bb5

bb5:
  br label %bb6

bb6:
  %10 = load i64, ptr %_0, align 8
  ret i64 %10
}

Meta

rustc --version --verbose:

rustc 1.98.0-nightly (bc2112ed5 2026-06-18)
binary: rustc
commit-hash: bc2112ed56c99fa649e09ab3ab286afab3d9059a
commit-date: 2026-06-18
host: x86_64-unknown-linux-gnu
release: 1.98.0-nightly
LLVM version: 22.1.7

Metadata

Metadata

Assignees

Labels

A-mir-optArea: MIR optimizationsC-bugCategory: This is a bug.I-miscompileIssue: Correct Rust code lowers to incorrect machine codeP-criticalCritical priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.regression-from-stable-to-stablePerformance or correctness regression from one stable version to another.

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions