Unexpected codegen for Cortex-R5 without FPU

I'm writing bare-metal/no-std code targeting the Arm Cortex-R5 processor, which does not have a floating-point unit (FPU) [1].

When compiling code that uses floats, rustc/llvm generates code that contain instructions not available on that processor. I'm trying to understand if this is due to:

  1. wrong expectations on my side;
  2. a bug in the Rust compiler;
  3. a bug in LLVM.

For example the code:

fn add_s(a: f32, b: f32) -> f32 {
    a + b
}

fn add_d(a: f64, b: f64) -> f64 {
    a + b
}

Is compiled to the following assembly (godbolt link), using rustc version 1.79.0 with flags --target armv7r-none-eabi -C opt-level=3 -C target-cpu=cortex-r5.

add_s:
        vmov    s0, r1
        vmov    s2, r0
        vadd.f32        s0, s2, s0
        vmov    r0, s0
        bx      lr

add_d:
        vmov    d0, r2, r3
        vmov    d1, r0, r1
        vadd.f64        d0, d1, d0
        vmov    r0, r1, d0
        bx      lr

Note the vadd.f32 and vadd.f64 instructions, which are not available on Cortex-R5 and cause the bare-metal program to halt.

If I don't set the target CPU, codegen is fine.

rustc flags: --target armv7r-none-eabi -C opt-level=3

add_s:
        push    {r11, lr}
        bl      __aeabi_fadd
        pop     {r11, pc}

add_d:
        push    {r11, lr}
        bl      __aeabi_dadd
        pop     {r11, pc}

Now I have some questions about this behavior:

  1. Am I right in expecting the target armv7r-none-eabi to generate code without FPU instructions and that therefore should run on the Cortex-R5? [2]
  2. If yes, why does specifying the target CPU causes illegal instructions to be generated?
  3. Is there a way to see what rustc and llvm features are enabled by passing -C target-cpu=cortex-r5? I searched through the rustc repo, but couldn't find it.
  4. Is this a bug in either rustc or llvm?

Thank you for your help!


Edit: modified example code to show both single-precision (f32) and double-precision (f64) versions of the code and generated assembly.


  1. contrary to the Cortex-R5F which does have an FPU ↩︎

  2. I would assume that if I wanted to target the Cortex-R5F and have FPU instructions, I should use the target armv7r-none-eabihf instead. ↩︎

The upstream LLVM CPU model for cortex-r5 includes the flag FeatureVFP3_D16; this is almost certainly the root cause of your surprise illegal instructions, and where the bug lurks, since that tells LLVM that the Cortex-R5 has floating point instructions.

Compare the R4 and R4F definitions directly above in that file, where the R4 does not have floating point features enabled, but the R4F does.

1 Like

Thanks, that does look like a problem. (I added an f64 version above for completeness).

I tried to manually disable the feature vfp3d16 by adding -C target-feature=-vfp3d16[1] to the command, but the generated assembly still shows both vadd.f32 and vadd.f64 instructions (godbolt link).

I also tried to manually disable all features that had vfp in their name, but then the code doesn't compile and I get an error from llvm (godbolt link):
Flags: --target armv7r-none-eabi -C opt-level=3 -C target-cpu=cortex-r5 -C target-feature=-vfp2,-vfp3,-vfp4,-vfp2sp,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4d16,-vfp4d16sp,-vfp4sp

rustc-LLVM ERROR: Cannot select: 0x7819fb38d150: f64 = fadd 0x7819fb38d070, 0x7819fb38d380, example.rs:10:5
  0x7819fb38d070: f64 = ARMISD::VMOVDRR 0x7819fb38d700, 0x7819fb38d1c0
    0x7819fb38d700: i32,ch = CopyFromReg 0x7819fb2350e0, Register:i32 %0
      0x7819fb38d8c0: i32 = Register %0
    0x7819fb38d1c0: i32,ch = CopyFromReg 0x7819fb2350e0, Register:i32 %1
      0x7819fb38d620: i32 = Register %1
  0x7819fb38d380: f64 = ARMISD::VMOVDRR 0x7819fb38d7e0, 0x7819fb38d690
    0x7819fb38d7e0: i32,ch = CopyFromReg 0x7819fb2350e0, Register:i32 %2
      0x7819fb38d850: i32 = Register %2
    0x7819fb38d690: i32,ch = CopyFromReg 0x7819fb2350e0, Register:i32 %3
      0x7819fb38d770: i32 = Register %3
In function: _ZN7example5add_d17h453d1a5317fb08abE
Compiler returned: 101

Any idea what's going on?


  1. I got the feature name from the output of rustc --target armv7r-none-eabi --print=target-features. ↩︎

At a guess, LLVM is still trying to use hardware FP; try adding +soft-float to your list of target features to persuade it that HW FP is not an option, as in this soft-float godbolt link

2 Likes

Thanks, yes +soft-float does it!

I now have an annoying warning from the compiler, but I guess I'll have to live with it:

warning: unknown and unstable feature specified for `-Ctarget-feature`: `soft-float`
  |
  = note: it is still passed through to the codegen backend, but use of this feature might be unsound and the behavior of this feature can change in the future
  = help: consider filing a feature request

warning: 1 warning emitted

I would also, if I were in your shoes, file an issue (against LLVM if you're confident reproducing the problem with Clang, or against Rust if you'd like the Rust compiler devs to help you get the bug report correct).

The chances are good that LLVM simply didn't know that the R5 and R5F existed as separate cores; but you want to get this reported rather than depending on workarounds so that you don't get caught out by an update changing the feature flags underneath you, or even removing the ability to disable the F part of the R5 model.

1 Like

I started with an issue against Rust:

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.