-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
asm! macro compiles fine on debug build but not on release build #61429
Comments
I just tested this on Windows as well and the same problem there so this does not seem to be platform specific.
I also narrowed down the minimal example to reproduce: #![feature(asm)]
#[derive(Default)]
struct ThreadContext;
fn gt_switch(new: *const ThreadContext) {
unsafe {
asm!("
mov 0x00($0), %rsp
ret
"
:
: "m"(new)
);
}
}
fn main() {
let ctx = ThreadContext::default();
gt_switch(&ctx);
} The code will segfault if run of course but since this is a compile time error the interesting part is the compilation. It seems to be related to the |
Not actually a bug, I think. Or at least a complicated one. Can fix this code by getting rid of the unneeded
or changing the constraint to The real bug here is that the original code compiles on debug. On my box I get
for the offending line in debug mode, which has somehow both removed the offending offset and left |
Well, I'm not 100% sure either if it working in debug is a bug or not working in release is a bug. What I do know is that the code works fine on debug doing what I would expect it to do. And the above is a contrived example, and the smallest I could make. The real code looks like this: unsafe fn switch(old: *mut ThreadContext, new: *const ThreadContext) {
asm!("
mov %rsp, 0x00($0)
mov %r15, 0x08($0)
mov %r14, 0x10($0)
mov %r13, 0x18($0)
mov %r12, 0x20($0)
mov %rbx, 0x28($0)
mov %rbp, 0x30($0)
mov 0x00($1), %rsp
mov 0x08($1), %r15
mov 0x10($1), %r14
mov 0x18($1), %r13
mov 0x20($1), %r12
mov 0x28($1), %rbx
mov 0x30($1), %rbp
ret
"
: "=*m"(old)
: "r"(new)
:
: "alignstack" // needed to work on windows
);
} Which according to the LLVM Reference should work (I think): https://llvm.org/docs/LangRef.html#indirect-inputs-and-outputs
What seems to happen is that the compiler inserts an additional "offset" in release mode, that somehow compiles in debug mode, but its pretty hard to debug... |
If you want to play around with a full working example that actually runs something useful, I have one here: |
So I translated the original example into C and tried it with Clang and GCC. Neither one would take it with an "m" or "*m" constraint: needs to be "r" or "*r". By the way probably want to clobber "rsp" in that |
OK, so I decided to try out something else. Since there really isn't any #[naked]
unsafe fn switch(old: *mut ThreadContext, new: *const ThreadContext) {
asm!("
mov %rsp, 0x00(%rdi)
mov %r15, 0x08(%rdi)
mov %r14, 0x10(%rdi)
mov %r13, 0x18(%rdi)
mov %r12, 0x20(%rdi)
mov %rbx, 0x28(%rdi)
mov %rbp, 0x30(%rdi)
mov 0x00(%rsi), %rsp
mov 0x08(%rsi), %r15
mov 0x10(%rsi), %r14
mov 0x18(%rsi), %r13
mov 0x20(%rsi), %r12
mov 0x28(%rsi), %rbx
mov 0x30(%rsi), %rbp
ret
"
:
:"{rdi}"(old), "{rsi}"(new)
:
: "volatile", "alignstack"
);
} Now this works as expected when running debug, but again fails in release build. You can easily try this out yourself in the playground.. Now there are no compilation error this time but a runtime error so I decided to output the Debug movq 8(%rsp), %rdi
movq 16(%rsp), %rsi
.cv_loc 9 1 155 0
#APP
movq %rsp, (%rdi)
movq %r15, 8(%rdi)
movq %r14, 16(%rdi)
movq %r13, 24(%rdi)
movq %r12, 32(%rdi)
movq %rbx, 40(%rdi)
movq %rbp, 48(%rdi)
movq (%rsi), %rsp
movq 8(%rsi), %r15
movq 16(%rsi), %r14
movq 24(%rsi), %r13
movq 32(%rsi), %r12
movq 40(%rsi), %rbx
movq 48(%rsi), %rbp
retq Release cmpq %rdx, %r8
jbe .LBB8_7
movq (%rcx), %rax
movb $1, 88(%rax,%rsi)
movq 16(%rcx), %r8
movq 24(%rcx), %rax
movq %rdx, 24(%rcx)
cmpq %rax, %r8
jbe .LBB8_15
cmpq %rdx, %r8
jbe .LBB8_13
movq (%rcx), %rcx
leaq (%rax,%rax,2), %rax
shlq $5, %rax
leaq (%rcx,%rax), %rdi
addq $32, %rdi
addq %rcx, %rsi
addq $32, %rsi
#APP
movq %rsp, (%rdi)
movq %r15, 8(%rdi)
movq %r14, 16(%rdi)
movq %r13, 24(%rdi)
movq %r12, 32(%rdi)
movq %rbx, 40(%rdi)
movq %rbp, 48(%rdi)
movq (%rsi), %rsp
movq 8(%rsi), %r15
movq 16(%rsi), %r14
movq 24(%rsi), %r13
movq 32(%rsi), %r12
movq 40(%rsi), %rbx
movq 48(%rsi), %rbp
retq Now I know the offset in the first part of the movq 8(%rsp), %rdi
movq 16(%rsp), %rsi I figured this out by diffing the files (sorry for the picture), but left is with Now I can't seem to wrap my head around how the When I diffed them the same way as i diffed the debug asm output there is absolutely no difference in the output at all which I find strange. I don't even know if this is related in any way and if the way It seems that this is more a LLVM issue than a Rust issue to be honest. |
So this is the status after quite a bit of debugging on my part:
|
Interesting. Consider this C code:
This produces bogus assembly on both GCC 8.3.0 and Clang 7.0.1. Honestly, it should. The only way that the requested load can be written is as fetch from the struct: the struct is sitting on the stack at an offset from The way Rust is getting away with this in the debug code is by pre-moving the struct address into a register before expanding the asm: this allows the thing to succeed. This is arguably a bug in movq -8(%rbp), %rdi
.loc 6 8 8
#APP
movq (%rdi), %rsp I think a cleaner and simpler way to write this example is as follows. It prevents a bunch of indexing errors, and makes it clear to LLVM what is being done with the registers. (struct cut down for clarity) #![feature(asm)]
struct ThreadContext {
rsp: u64,
r15: u64,
}
fn gt_switch(new: *const ThreadContext) -> ! {
unsafe {
asm!("mov $1, $0" : "+r"("r15") : "*m"(&(*new).r15));
asm!("mov $1, $0" : "+r"("rsp") : "*m"(&(*new).rsp));
asm!("ret");
std::hint::unreachable_unchecked()
}
}
fn main() {
let ctx = ThreadContext{rsp: 0x80, r15: 0x88};
gt_switch(&ctx);
} Unfortunately, compiling this (release or debug) results in this output:
Bumping to tonight's nightly gives the same result. Time to file another bug, I guess. |
Sigh. Someday I'll learn to write inline assembly. This looks like it outputs the right thing. At least it doesn't crash the compiler. #![feature(asm)]
struct ThreadContext {
rsp: u64,
r15: u64,
}
fn gt_switch(new: *const ThreadContext) -> ! {
unsafe {
asm!("mov $0, %r15" : : "*m"(&(*new).r15) : "r15");
asm!("mov $0, %rsp" : : "*m"(&(*new).rsp) : "rsp");
asm!("ret");
std::hint::unreachable_unchecked()
}
}
fn main() {
let ctx = ThreadContext{rsp: 0x80, r15: 0x88};
gt_switch(&ctx);
} |
Actually, those clobbers above appear to be a bad idea: they cause the compiler to save the clobbered registers gratuitously, even though it can tell that they will never be restored. Delete them. |
This issue does not apply to the new The legacy |
The following code compiles fine on Mac debug build, but fails on release build. The same happens on rust playground. This is just part of some code I use for debugging.
The remarkable thing is that in the larger example I have I use the same type of inline assembly and it compiles fine on --release, so I don't know what to conclude from this:
I tried this code (minimal example):
I expected it to compile on both release and debug build. Instead, i got this error when compiling for release:
Easiest steps to reproduce. See this playground link:
https://play.rust-lang.org/?version=nightly&mode=release&edition=2018&gist=23908e070df55393682c9a922c80085b
You will need to choose "run" or "show assembly", somehow if you choose build it builds without errors in the playground.
Meta
The text was updated successfully, but these errors were encountered: