Inline assembly is compiled to unexpected binary

Hello.

I have two functions and both of them are expected to return the given argument:

pub fn main() !void {
    log.debug("{X}", .{hoge1(0xDEADBEEF)});
    log.debug("{X}", .{hoge2(0xDEADBEEF)});
}

fn hoge1(arg1: u64) u64 {
    return asm volatile (
        \\movq %[arg1], %[ret]
        : [ret] "={rax}" (-> u64),
        : [arg1] "m" (arg1),
    );
}

fn hoge2(arg1: u64) u64 {
    return asm volatile (
        \\movq (%[arg1]), %[ret]
        : [ret] "={rax}" (-> u64),
        : [arg1] "r" (&arg1),
    );
}

hoge2() returns 0xDEADBEEF as expected. However, hoge1() returns stack address. The output assembly is below:

0000000001034c50 <main.hoge1>:
 1034c50:       55                      push   rbp
 1034c51:       48 89 e5                mov    rbp,rsp
 1034c54:       48 83 ec 18             sub    rsp,0x18
 1034c58:       48 89 7d f0             mov    QWORD PTR [rbp-0x10],rdi
 1034c5c:       48 89 7d f8             mov    QWORD PTR [rbp-0x8],rdi
 1034c60:       48 8d 45 f8             lea    rax,[rbp-0x8]
 1034c64:       48 89 45 e8             mov    QWORD PTR [rbp-0x18],rax
 1034c68:       48 8b 45 e8             mov    rax,QWORD PTR [rbp-0x18]
 1034c6c:       48 83 c4 18             add    rsp,0x18
 1034c70:       5d                      pop    rbp
 1034c71:       c3                      ret
 1034c72:       66 2e 0f 1f 84 00 00    cs nop WORD PTR [rax+rax*1+0x0]
 1034c79:       00 00 00
 1034c7c:       0f 1f 40 00             nop    DWORD PTR [rax+0x0]

0000000001034c90 <main.hoge2>:
 1034c90:       55                      push   rbp
 1034c91:       48 89 e5                mov    rbp,rsp
 1034c94:       48 83 ec 10             sub    rsp,0x10
 1034c98:       48 89 7d f0             mov    QWORD PTR [rbp-0x10],rdi
 1034c9c:       48 89 7d f8             mov    QWORD PTR [rbp-0x8],rdi
 1034ca0:       48 8d 45 f8             lea    rax,[rbp-0x8]
 1034ca4:       48 8b 00                mov    rax,QWORD PTR [rax]
 1034ca7:       48 83 c4 10             add    rsp,0x10
 1034cab:       5d                      pop    rbp
 1034cac:       c3                      ret
 1034cad:       0f 1f 00                nop    DWORD PTR [rax]

I expected that two of them are compiled to the virtually same assembly, but they are not.
I believe something in my code is wrong (I’m not familiar with asm).

I appreciate your advice.

NOTE: For your information, HERE is a C result in CompileExplorer.
NOTE: I’m using Zig 0.13.0.

It looks like it’s giving you two levels of indirection instead of one, so you need to de-reference the pointer again. I don’t know enough to determine whether this is a bug or not. Also, according to the docs, if you read or write to the location of an “m” constraint (i.e. use mov in x86), you must use an “indirect” input constraint “*m”. This will crash the compiler, so the only way to make this snippet correct is by clobbering memory:

fn hoge1(arg1: u64) u64 {
    return asm volatile (
        \\mov %[arg1], %[ret]
        \\mov (%[ret]), %[ret]
        : [ret] "={rax}" (-> u64)
        : [arg1] "m" (arg1)
        : "memory"
    );
}

You are taking the address of arg1 (&arg1) and putting it into a register (r). Then you are moving this address to rax. In order for arg1 to have an address, it needs to be spilled onto the stack, which is why you are getting a stack address.

1 Like

Thanks for your replies.
So Zig’s inline asm syntax is slightly different from clang’s one?
C equivalent code seems work for both cases: Compiler Explorer

This seems like a Zig compiler bug, although I’m hardly an inline assembly expert… Coincidentally someone created a relevant issue on GitHub three weeks ago: https://github.com/ziglang/zig/issues/21750.

1 Like