Raspbian version of libllvm7 miscompiles floating point code
Affects | Status | Importance | Assigned to | Milestone | |
---|---|---|---|---|---|
Raspbian |
Fix Released
|
Undecided
|
Unassigned |
Bug Description
I ran into a problem compiling the typenums rust package (things like `the name N1000 is defined multiple times` for anyone googling). I traced this down to a problem with the LLVM optimizer. However, replacing the `libllvm7` package with the version from Debian Buster armhf makes things work as expected, which makes me report this bug here.
Comparing the debian and Raspbian version of llvm-toolchain-7, it seems pretty much the only relevant change is using armv6 rather than arm7. This suggests that either something in the way this change is implemented by Raspbian is causing this problem, or upstream has a bug in their armv6 code generation that does not occur for armv7. I've also tried the below testcase with llvm-9, which does work. Looking at changelog.Debian the Raspbian-specific patches are unchanged so that suggests this might be an upstream problem in LLVM that was since fixed.
However, since Buster's rust compiler (and probably others) is linked against llvm-7, it might still be relevant to fix this.
To reproduce, here's the testcase. This is just a function that calculates the 2-log of 1024 (by calculating the natural log of 1024 and 2 and dividing them). I've also added functions that individually calculate these logs, so you can see that it's not the division itself triggering the problem.
$ cat log.ir
declare double @llvm.log.
define double @foo() {
start:
%0 = call double @llvm.log.
%1 = call double @llvm.log.
%2 = fdiv double %0, %1
ret double %2
}
define double @bar() {
start:
%0 = call double @llvm.log.
ret double %0
}
define double @baz() {
start:
%0 = call double @llvm.log.
ret double %0
}
With libllvm7 from Debian, this works as expected. The optimizer replaces the log calls with constants:
$ opt-7 log.ir -S -instcombine
; ModuleID = 'log.ir'
source_filename = "log.ir"
; Function Attrs: nounwind readnone speculatable
declare double @llvm.log.
define double @foo() {
start:
ret double 1.000000e+01
}
define double @bar() {
start:
ret double 0x401BB9D3BEB8C86B
}
define double @baz() {
start:
ret double 0x3FE62E42FEFA39EF
}
attributes #0 = { nounwind readnone speculatable }
With libllvm7 from Raspbian, this breaks. Every log call is replaced by a NaN value:
$ opt-7 log.ir -S -instcombine
; ModuleID = 'log.ir'
source_filename = "log.ir"
; Function Attrs: nounwind readnone speculatable
declare double @llvm.log.
define double @foo() {
start:
ret double 0x7FF8000000000000
}
In the original
define double @bar() {
start:
ret double 0x7FF8000000000000
}
define double @baz() {
start:
ret double 0x7FF8000000000000
}
attributes #0 = { nounwind readnone speculatable }
In the original `rustc -C print-after-all` output of the original testcase, I also noticed that the EarlyCSE transformation evaluates log(1024) incorrectly, but I could not find which opt option to use to reproduce this. I suspect this has the same underlying problem, though I'm not sure.
*** IR Dump After SROA ***
; Function Attrs: inlinehint nounwind nonlazybind readnone uwtable
define internal fastcc double @"_ZN3std3f6421
start:
%0 = call double @llvm.log.
%1 = call double @llvm.log.
%2 = fdiv double %0, %1
ret double %2
}
*** IR Dump After Early CSE w/ MemorySSA ***
; Function Attrs: inlinehint nounwind nonlazybind readnone uwtable
define internal fastcc double @"_ZN3std3f6421
start:
%0 = call double @llvm.log.
%1 = fdiv double 0x747BB550747BA440, %0
ret double %1
}
For completeness, here is (as somewhat stripped down version) of the original rust code that showed the problem:
$ cat main.rs
fn main() {
let highest: u64 = 1024;
let first2: u32 = (highest as f64).log(2.0) as u32 + 1;
let first10: u32 = (highest as f64).log(10.0) as u32 + 1;
let uints2 = (0..(highest + 1))
// Expected to print 1 to 1024, followed by powers of two starting at 2048 and powers of ten
// starting at 10000.
for u in uints2 {
print!("{}, ", u);
}
println!();
}
$ rustc -O main.rs
$ ./main
And here are some versions of installed packages:
ii libstd-
ii rustc 1.34.2+dfsg1-1+rpi1 armhf Rust systems programming language
ii libstd-
ii libllvm7:armhf 1:7.0.1-8+rpi1 armhf Modular compiler and toolchain technologies, runtime library
ii llvm-7 1:7.0.1-8+rpi1 armhf Modular compiler and toolchain technologie
I have run into another issue which I think may be related.
It seems that when building for armv6k llvm 7 defaults to assuming no FPU is available. Unfortunately it seems that rather than doing the sensible thing when an impossible combination of fpu and abi is specified and screaming and dying it silently compiles the code with the soft float ABI, but tags it with the hard float ABI.
Debian/Raspbian builds it's final llvm binaries using llvm (after building first stage binaries using g++), so it seems like there is probably an ABI breakage between the llvm libraries and the rest of the system, I can easily see how this could lead to mis-compilation.
The fix for that would seem to be to change the default FPU settings for armv6k builds, unfortunately I am having some difficulties there (which may well turn out to just be build hardware being flaky).