rust-lang / rust-lang/rust

Potentially-observable store gets elided: asm block does not act as a compiler fence

Open
#144,351 21 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

A-LLVM C-bug I-unsound P-high T-compiler
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

View all comments

I tried this code:

#![feature(atomic_from_mut)]

use std::arch::asm;
use std::sync::atomic::*;

unsafe extern "C" {
    fn bar();
}
#[unsafe(no_mangle)]
pub unsafe fn foo(y: *mut *mut i32) {
    let mut val: i32 = 0;
    let x: *mut i32 = &mut val;

    *x = 42; 
    asm!(""); // compiler_fence(Ordering::Release);
    AtomicPtr::from_mut(&mut *y).store(x, Ordering::Relaxed);
    loop {}
}

I would have expected the generated code to still contain the store of 42 in x, since the empty asm block must be considered to be a release fence by LLVM -- and then some other thread might wait for the value of y to change, and read through that pointer.

However, the resulting LLVM IR after optimizations is (godbolt: https://rust.godbolt.org/z/r86soTWaa):

define void @foo(ptr nocapture noundef writeonly %y) unnamed_addr {
start:
  %val = alloca [4 x i8], align 4
  call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %val)
  tail call void asm sideeffect alignstack inteldialect "", "~{dirflag},~{fpsr},~{flags},~{memory}"() #2
  store atomic ptr %val, ptr %y monotonic, align 8
  br label %bb2

bb2:
  br label %bb2
}

If I replace the asm block by an actual fence or even just a compiler_fence, the result looks as expected:

define void @foo(ptr nocapture noundef writeonly %y) unnamed_addr {
start:
  %val = alloca [4 x i8], align 4
  call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %val)
  store i32 42, ptr %val, align 4
  fence syncscope("singlethread") release
  store atomic ptr %val, ptr %y monotonic, align 8
  br label %bb1

bb1:
  br label %bb1
}

This is based on an example by @Amanieu.

@nikic this is a bug, right? Surely an empty asm block with all clobbers must have at least all the effects of an arbitrary compiler fence?

@fg-cfh points out that the LLVM documentation for the memory clobber just says that the asm block can write arbitrary memory, but does not mention reading memory. The corresponding docs for GCC also mention reading. I assume this can only be an oversight, since surely in general asm! blocks are allowed to read memory they have access to.

However, I think this problem here is more subtle: the asm block couldn't read from *x after all, since at the point the asm block runs, neither x nor val have been escaped. The issue is that reading and writing are not the only things one can do with memory. We have to also consider "synchronization effects", such as release/acquire fences, which are not associated with a memory access at all -- they change some other part of global state which records which memory events the next relaxed store can "publish" to other threads (and which events the next acquire fences "receives" into the current thread).

Cc @rust-lang/opsem

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the Rust reproducer in the issue and compare its optimized LLVM IR with the versions using an actual fence or compiler_fence. Review the asm! memory-clobber semantics and the linked LLVM documentation, then determine whether the observed elision violates the intended contract. Done means the semantics are resolved and any required compiler or documentation change is validated.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.