Skip to content

semantics of black_box and clobber in the memory model #311

Closed
@gnzlbg

Description

@gnzlbg

I've just closed rust-lang/rfcs#2360 due to @rkruppe's input that by trying to specify what black_box and clobber do there the RFC is basically specifying a subset of the memory model.

So I'd like to ask feedback for these here first.

The specification in the RFC is very loose. mem::black_box(x) is specified as follows:

  • writes the address of x to memory
  • reads all memory
  • writes all memory

while mem::clobber():

  • reads all memory
  • writes all memory

where I have no idea how to specify memory but @rkruppe came up with a minimal example that helps:

{
    let tmp = x;
    clobber();
}

Here, the compiler is allowed to optimize the store of x to tmp away, because the only code that has the address of tmp is in that scope, and that code does not read or write from tmp. In the specification of clobber, when it states "read/write from/to all memory", "memory" does not refer to tmp. However, if I change the example to:

{
    let tmp = x;
    black_box(&tmp);
    clobber();
}

in this case clobber() requires the store of x to tmp to be live, because black_box has written the address of tmp to "memory", and thus the "read/write to/from memory" in clobber can do something with tmp.

So a big question I have is what is "memory" here, and how does tmp in the first example differs from the rest?

I don't know how these could make sense in Rust memory model, what the wording for their specification should be, and I am barely qualified to follow the discussion at all. The RFC contains more information, and lots of godbolt links, but black_box and clobber proposed implementaiton is this:

#[inline(always)]
fn clobber() {
    unsafe { asm!("" : : : "memory" : "volatile") };
}

#[inline(always)]
fn black_box<T>(x: T) -> T {
    unsafe {
        asm!("" // template
          : // output operands
          : "r"(&x) // input operands
          // r: any general purpose register
          : "memory"    // clobbers all memory
          : "volatile"  // has side-effects
        );
        x
    }
}

Also, @rkruppe asked:

I'd like to know the difference between this and compiler_fence(SeqCst)

To which @nagisa answered:

The difference between asm! with a memory clobber and compiler_fence exists in the fact, that memory clobber requires compiler to actually reload the memory if they want to use it again (as memory is… clobbered – considered changed), whereas compiler_fence only enforces that memory accesses are not reordered and the compiler still may use the usual rules to figure that it needn’t to reload stuff.


cc @rkruppe @nagisa

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions