Skip to content

Volatile not respected in some edge cases #21033

@kibels

Description

@kibels

Zig Version

0.13.0

Steps to Reproduce and Observed Behavior

Taking a volatile pointer to a nested array of packed structs (such as an array of machine registers) causes loads to not be volatile which can lead to bad machine code like infinite loops being generated

The minimal repro I could achieve is this:
https://godbolt.org/z/dcrae4639
This while loop doesn't execute the ldr(line 5 in the 0.12 asm) again each loop on arm or riscv32 since at least 0.13, which causes an infinite loop if polling a machine register for a change. In 0.12 it could be worked around by populating the loop.

In the latest versions it can still be worked around by putting a memory clobber in the loop. Notably this doesn't repro on x86 but does repro on at least aarch64, thumb, and riscv32.

This affects the mmio.Mmio struct used for accessing machine registers in Microzig in some cases, so does have some impact for common embedded use cases.

Copy of code in case godbolt gets lost:

// Build args: -target thumb-freestanding -OReleaseSmall
const struct_view = packed struct(u32) {
    raw: u32,
};
const Nested = extern struct {
    bad: [1]struct_view,
    good: struct_view,
};
export fn main_bad() void {
    const mmio_ptr: *volatile Nested = @ptrFromInt(0x40000000);
    while (mmio_ptr.bad[0].raw == 0) {} // Skips the load on repeated loops
    //     ldr     r0, [r0]   // <- Load outside the loop
    // .LBB0_1:
    //     cbnz    r0, .LBB0_3
    //     b       .LBB0_1
    // .LBB0_3:
}
export fn main_good() void {
    const mmio_ptr: *volatile Nested = @ptrFromInt(0x40000000);
    while (mmio_ptr.good.raw == 0) {} // Does volatile load of register
    // .LBB0_1:
    //     ldr     r2, [r0]   // <- Load inside the loop
    //     cbnz    r2, .LBB0_3
    //     b       .LBB0_1
    // .LBB0_3:
}

Expected Behavior

Expect loads through *volatile to always be treated as volatile.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behaviormiscompilationThe compiler reports success but produces semantically incorrect code.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions