-
Notifications
You must be signed in to change notification settings - Fork 14
[LLVM 6.0] core::fmt::Debug implementations causing 'error: ran out of registers' #95
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
Comments
In the case of Here is the live interval that it is failing on
That variable is only live over the slot range Look for the rows 76-92 inclusive.
|
I suspect that this is not actually related to the format struct being passed fully in registers, because that logic is fully decided by the calling convention, and LLVM would not be able to "optimize" this because it'd break the ABI. |
Note that the ICALL instruction itself does not take any operands - all of its inputs and outputs are implicit. ICALL always executes the function pointed to by This problem could simply be ordinary register pressure, combined with avr-llvm/llvm#1. Surprising that this didn't occur on the previous LLVM version. |
I found an oldish LLVM build on my computer, built on Oct 5th 2017 The binaries were compiled at this commit
Note the "avr-rust-llvm-release-4-0-1". This is the current version avr-rust/rust uses. I've ran the same testcase in the description, and it too failed on this piece of code. This means that whatever AVR backend bug is being triggered, it has existed since at least LLVM 4.0. I wonder if current Rust itself is generating different LLVM IR for this function than it used to. That would explain why this has only come up now. |
The LLVM IR calls a function via a function pointer derived from the define void @"_ZN65_$LT$lib..str..Chars$LT$$u27$a$GT$$u20$as$u20$lib..fmt..Debug$GT$3fmt17h76a537e22649f739E"(%"fmt::Formatter.1.77.153.229.305.381.1673"* %__arg_0) {
start:
%0 = getelementptr %"fmt::Formatter.1.77.153.229.305.381.1673", %"fmt::Formatter.1.77.153.229.305.381.1673"* %__arg_0, i16 0, i32 11, i32 0
%1 = load {}*, {}** %0, align 1
%2 = getelementptr %"fmt::Formatter.1.77.153.229.305.381.1673", %"fmt::Formatter.1.77.153.229.305.381.1673"* %__arg_0, i16 0, i32 11, i32 1
%3 = bitcast {}** %2 to i1 ({}*, [0 x i8]*, i16)***
%4 = load i1 ({}*, [0 x i8]*, i16)**, i1 ({}*, [0 x i8]*, i16)*** %3, align 1
%5 = getelementptr i1 ({}*, [0 x i8]*, i16)*, i1 ({}*, [0 x i8]*, i16)** %4, i16 3
%6 = load i1 ({}*, [0 x i8]*, i16)*, i1 ({}*, [0 x i8]*, i16)** %5, align 1
; %7 = call i1 %6({}* %1, [0 x i8]* bitcast ([5 x i8]* @str.4S to [0 x i8]*), i16 5)
%7 = call i1 @foobar({}* %1, [0 x i8]* bitcast ([5 x i8]* @str.4S to [0 x i8]*), i16 5)
unreachable
} The The expanded implementation looks like this #[stable(feature = "rust1", since = "1.0.0")]
pub struct Chars<'a> {
iter: slice::Iter<'a, u8>,
}
#[automatically_derived]
#[allow(unused_qualifications)]
#[stable(feature = "rust1", since = "1.0.0")]
impl <'a> ::fmt::Debug for Chars<'a> {
fn fmt(&self, __arg_0: &mut ::fmt::Formatter) -> ::fmt::Result {
match *self {
Chars { iter: ref __self_0_0 } => {
let mut builder = __arg_0.debug_struct("Chars");
let _ = builder.field("iter", &&(*__self_0_0));
builder.finish()
}
}
}
} |
Here is the code prior to register allocation with optimisations /bin/llc -march=avr -mcpu=atmega328p ran-out-of-regs.ll -o /dev/null -O2 -print-before-all 2>&1|less
Here is the code prior to register allocation with no optimisations /bin/llc -march=avr -mcpu=atmega328p ran-out-of-regs.ll -o /dev/null -O2 -print-before-all 2>&1|less
The only differences I can see
|
Here's a full LLVM MIR dump of the reproduction, taken just prior to register allocation.
|
N.B. The function in question does not have a frame pointer |
These are all "regular" Rust functions, which means that Rust itself gets to determine the calling convention. This can (and has) changed over time. I even believe that with inlining, the calling convention could go out the window, so long as there's no "external visibility" of the change. |
I checked for professional opinions in #rustc:
irc://irc.oftc.net/#llvm |
Something I wrote after skimming the first post here. Probably not true. Stuff I’d do first is double check whether the tables for machine instructions are correct and maybe try alternative register allocator instead of fast-reg-alloc. |
Isn't this the same bug as #37? |
Yes, but I raised this because the frequency of the bug seemed to occur a lot more under LLVM 6.0. IIRC LLVM 6.0, this issue didn't affect For example, here is the same debug implementation above that LLVM 6 had a problem with. This has not been cfg'd out. |
We've recently upgraded to LLVM 8 🎉 I'm going to close any bug that is reported against an older version of LLVM. If you are still having this issue with the LLVM-8 based code, please ping me and I can reopen the issue! |
A large number of
core::fmt::Debug::fmt
implementations are causing 'ran out of registers' error. This problem has come up in the past (avr-llvm/llvm#1), but that generally happens with quite complex code. Even fairly trivial fmt functions are causing this error.This is the error message
Here is the reproduction code
I have also attached the full libcore LLVM IR that generates this error.
Here is what the function looks like during register allocation when the error is actually hit.
@shepmaster suspects that:
core-noregs.ll.txt
The text was updated successfully, but these errors were encountered: