Proper way to expose two `extern` items that *may alias*?

From C, or the linker, or something outside Rust, I receive two items. Call them _data and _edata.

extern {
  static mut _data: u32;
  static mut _edata: u32;
}

The two symbols will be mapped to addresses by the linker, and those addresses may be the same. Not every build, but sometimes.

rustc seems to perform optimizations under the assumption that the two items occupy different addresses, when they are defined as above. In particular, a loop like the following:

let mut src: *const u32 = &_data_load;
let mut dst: *mut u32 = &mut _data;

while dst != &mut _edata {
    *dst = *src;
    dst = dst.offset(1);
    src = src.offset(1);
}

...gets compiled into:

 8000058:       f851 3b04       ldr.w   r3, [r1], #4
 800005c:       f840 3b04       str.w   r3, [r0], #4
 8000060:       4282            cmp     r2, r0
 8000062:       d1f9            bne.n   8000058

...meaning that the initial while loop test is assumed constant. If _data and _edata have the same address this code misbehaves. Good optimization in general, but incorrect for this case.

I can fix the compiler's output for this loop by switching != to <, but that's just avoiding the issue.

I understand that having two objects that may or may not overlap makes a whole bunch of things potentially unsafe. I am doing something rather unsafe. So assuming I'm already in this situation, is there a different way I can declare the externs to tell the compiler that they are potentially aliased, to avoid this optimization biting me in a later build?


Note: I'm writing the equivalent of crt0.o. Other people I've seen do this, e.g. in Zinc, put dummy data between _data and _edata to force them not to alias.

Note that this isn't just a Rust issue: Clang makes the same assumption. For reference, simplified Rust example.

It looks like declaring the variables as [u32; 0] (or any zero-sized type) hinders the optimization, but I don't know if LLVM documents its assumptions around this anywhere.

1 Like

Try marking the struct fields as UnsafeCell<u32> instead of just u32.

Yeah, that occurred to me, since the compiler makes fewer aliasing guarantees when it notices the use of UnsafeCell (though I think that's only for references). But it doesn't seem to work (unless I've done it wrong here):

extern {
    static _data_load: UnsafeCell<u32>;  // or mut
    static mut _data: UnsafeCell<u32>;
    static mut _edata: UnsafeCell<u32>;
}

unsafe fn initialize_data() {
    let mut src: *const u32 = _data_load.get();
    let mut dst: *mut u32 = _data.get();

    while dst != _edata.get() {
        *dst = *src;
        dst = dst.offset(1);
        src = src.offset(1);
    }
}

Produces:

 8000042:       f240 1010       movw    r0, #272        ; 0x110
 8000046:       f240 0100       movw    r1, #0
 800004a:       f240 0204       movw    r2, #4
 800004e:       f6c0 0000       movt    r0, #2048       ; 0x800
 8000052:       f2c2 0100       movt    r1, #8192       ; 0x2000
 8000056:       f2c2 0200       movt    r2, #8192       ; 0x2000
 800005a:       f850 3b04       ldr.w   r3, [r0], #4
 800005e:       f841 3b04       str.w   r3, [r1], #4
 8000062:       428a            cmp     r2, r1
 8000064:       d1f9            bne.n   800005a

which skips the condition check on the first iteration of the loop because it assumes _data.get() != _edata.get().

1 Like