Why does a `lock` prefix in `asm` make the program without output?

use std::arch::asm;
fn main() {
    let mut expect = 1;
    let mut new = 1;
    let mut save = 2;
    unsafe {
        asm!(
            "mov eax, ecx",
            "lock cmpxchg {save:e},{new:e}",
            "mov ecx, eax",
             save = inout(reg) save,
             new = inout(reg) new,
             inout("ecx") expect
        );
    };
    println!("aaa == {expect}");
}

If remove the lock in the second line in the asm! macro, then the content in the println! will be printed in the terminal. What's the problem here?

I believe this is more an ASM question than a Rust question; my interpretation from this page is that you can't use lock cmpxchg with registers. (I'm no expert in this area though.)

I change the first operand to a memory location, but the issue is still not solved. I find that save will be substituted with rax such that the first operation always be the value of expect, which is not a memory location. I don't know whether it's a bug or a feature of the compiler.

When I checked the asm out of the OP, the only difference was the asm line in question, so I still feel any problem is there.

You don't present any code thus it's hard to say for sure.

But lock cmpxchg works as expected.

Not sure what you were trying to achieve with your code, it's wrong on so many levels it's just not funny.

Yes, usually lock cmpxchg without memory is an illegal instruction.

There are one known exception and there may be others but I seriously doubt that topicstarter uses something like that.

2 Likes
fn main() {
    let mut expect = 1;
    let mut new = 1;
	let mut b = vec![1,3];
    let mut save = b.as_mut_ptr();
    unsafe {
        asm!(
            "mov rax, rcx",
            "lock cmpxchg [{save}],{new}",
            "mov rcx, rax",
             save = in(reg) save,
             new = in(reg) new,
             inout("rcx") expect
        );
    };
    println!("aaa == {expect}");
}
use std::{arch::asm, vec};

Rust Playground.

In this example, the compiler use rax to replace save, which always cause the exceptions.

In that example you promised to the compiler then you wouldn't touch rax. And you changed it. That's UB thus you are getting what you are deserving.

Where do I promise to the compiler that I won't touch rax. Didn't I use the rax in the first line?

Ughm…

Yes. And that's violation of your binding promise.

Compiler doesn't look inside of your asm block (it counts instructions in there to know it's size, but that's it), it only looks on the operands you specified. You specified three registers which means you got to touch three registers (you can only touch memory by default in Rust, in С/C++ you would need to do that explicitly, too). If you need more you need to specify them as clobbered.

So, how do I need to do to promise compiler that I will use rax?

You can declare it as clobbered register. Or just specify it as third argument (inout("rax") expect).

Then compiler would know you are touching rax (and would know how you are touching it: read, write or read-write).

Thanks. How to define a register as a clobbered register?

Is there a reason you're not just using compare_exchange
or compare_exchange_weak

Just use a dummy variable as written in the documentation.

In general it's good idea to read the documentation for the feature you are using.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.