-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Issues/miscompilation around ARM T32 frame pointer with new asm syntax #73450
Comments
In case anyone else is doing something really strange with registers and runs into this, the workaround is to not mention either of the registers that rustc doesn't like, and save/restore them in the For example, a sequence that relies on taking inputs in both asm!("
push {{r7, r11}}
mov r7, {dest}
mov r11, {sysnum}
svc #0
pop {{r7, r11}}
",
dest = in(reg) dest.as_mut_ptr(),
sysnum = const Sysnum::BorrowRead as u32,
); Note that the asm!("
mov {save7}, r7
move {save11}, r11
mov r7, {dest}
mov r11, {sysnum}
svc #0
mov r7, {save7}
move r11, {save11}
",
dest = in(reg) dest.as_mut_ptr(),
sysnum = const Sysnum::BorrowRead as u32,
save7 = out(reg) _,
save11 = out(reg) _,
options(nostack),
); Whether this is higher or lower cost than |
In my opinion, this makes the constraints a _lot_ easier to read, and gets us things like noreturn. (Plus, it moves us off a deprecated feature whose days are numbered.) asm has opinions about trying to use r11. These opinions are misguided: rust-lang/rust#73450 However, we respect them by manually moving values in and out of r11 in the asm sequences.
This completes the sequence of workarounds for: rust-lang/rust#73450 r7 is the frame pointer. asm! cannot reason about clobbers of r7. It tries to statically refuse it, but statically refuses r11 instead. This avoids clobbering r7.
Given that this also happens with |
Not sure! I mostly use LLVM through rustc these days.
|
In my opinion, this makes the constraints a _lot_ easier to read, and gets us things like noreturn. (Plus, it moves us off a deprecated feature whose days are numbered.) asm has opinions about trying to use r11. These opinions are misguided: rust-lang/rust#73450 However, we respect them by manually moving values in and out of r11 in the asm sequences.
This completes the sequence of workarounds for: rust-lang/rust#73450 r7 is the frame pointer. asm! cannot reason about clobbers of r7. It tries to statically refuse it, but statically refuses r11 instead. This avoids clobbering r7.
"Not honoring clobbers properly" is definitely an LLVM bug. Sadly resolving it is non-trivial because LLVM uses the frame pointer to access the stack which is needed to save/restore register values. It seems that LLVM 11 has added a check (ARM-only for now) on writing to reserved registers: https://reviews.llvm.org/D76848. However it would still be preferable to catch this in the front-end. FYI here is the list of reserved registers on ARM:
|
If LLVM isn't capable of saving and restoring a base pointer around an asm block with a conflicting clobber, then, yes, for correctness, we probably have to ban r6. Having written some register allocators myself, I am disappointed with LLVM's behavior in these cases, but I imagine it simplifies things considerably for them. Two questions though:
|
Sure, you just need to add the
Not really, "disabling" the frame pointer just tells the compiler that it doesn't have to use a frame pointer all the time, only when it is needed. A frame pointer is generally needed when you have dynamically-sized objects on the stack ( |
Understood, thank you! |
Fix handling of reserved registers for ARM inline asm `r6` is now disallowed as an operand since LLVM sometimes uses it as a base pointer. The check against using the frame pointer as an operand now takes the platform into account and will block either `r7` or `r11` as appropriate. Fixes rust-lang#73450 cc @cbiffle
The implications of preventing the user from talking about clobbering a register, when that register is in fact open to use by the register allocator, are subtle. (To me, at least.) For example, here is a lightly contrived syscall stub. asm!("
mov {save11}, r11
mov r11, {in11}
svc #0
mov r11, {save11}
",
save11 = out(reg) _,
in11 = in(reg) something_or_other,
options(preserves_flags)
); Here, because the You see, in a complex enough function, LLVM eventually assigns the This is a potential issue because the use of the framepointer/basepointer registers is unpredictable. My understanding of the current implementation is that it is not possible to express this sequence correctly using inline Is that correct? (Testing this assertion is difficult -- the miscompilation happens only in certain shapes of complex functions -- and I'm not yet familiar enough with the rustc-LLVM interface to check for myself.) Or can we guarantee that the compiler won't allocate |
You can push
Unfortunately LLVM won't let us "reserve" |
Summary
First: the new
asm!
syntax reservesr11
on ARM as a frame pointer. This is incorrect on Thumb targets, where it should ber7
. (llvm_asm!
gets this right.)Second: if you attempt to use
r7
, things miscompile. (This is true ofllvm_asm!
as well, and is the actual issue that brought me here.)Code
I'm having a hard time writing a minimal reproduction, because you need to create a function with sufficient complexity to trigger reliance on the frame pointer. Here's what I've got.
Imagine that we have an OS syscall, invoked on ARM by the
svc
instruction, that needs to receive one parameter inr7
. (This is simplified from our actual ABI, but we do user7
for [reasons].)We most often see this when a function (syscall stub) containing a block like that gets inlined into another function, but we've also encountered it within a single small function.
In the disassembly, we see stuff like this:
If one were to use
r11
instead (which, spoiler, we also use), one would get a compiler error:...which is actually wrong for my target, but the intent is right: it appears to be trying to guard against this miscompilation case.
Expectations
I would normally expect that an explicit clobber would be enough for the compiler to not assume a particular value is live in a register across the
asm!
statement. This isn't true in eitherllvm_asm!
or the new syntax. (I tried the new syntax to see if it fixed this -- and also because I expected a chilly reception to a bug in a deprecated syntax.)Moreover, I argue that it is a misfeature to prevent the user from describing an
asm
sequence that damages the frame pointer. Such sequences can absolutely occur, particularly in cases of register bank switching, context restore, or naked functions that manage their own frames. The restriction on naming frame pointer should be loosened, in my opinion; the compiler can always generate code to save/restore it. (I'm currently ok with the ban on namingsp
orpc
.)But barring that, the restriction should be correct.
Meta
This miscompilation was initially observed using
llvm_asm!
on the 2020-05-01 nightly. Confirmed with the newasm!
syntax on:rustc --version --verbose
:The
asm!
announcement said I should addF-asm
to this report, but it is not apparent how I would do that.Hat tip to @labbott for identifying this.
The text was updated successfully, but these errors were encountered: