Skip to content

thumbv6m-none-eabi compiler builtin ABI bug #39056

Closed
@dnseitz

Description

@dnseitz

I'm helping to build a small operating system to run on Cortex-M0 processors. I'm compiling for the thumbv6m-none-eabi target. When using the compiler builtin function __aeabi_lmul it seems that the compiler is passing in the arguments to the wrong registers. For example, this piece of code:

fn do_mul(a: isize, b: isize) -> isize {
  a * b
}

Gets compiled down to:

   0:   b580            push    {r7, lr}
   2:   af00            add     r7, sp, #0
   4:   b088            sub     sp, #32
   6:   460a            mov     r2, r1
   8:   4603            mov     r3, r0
   a:   9004            str     r0, [sp, #16]
   c:   9105            str     r1, [sp, #20]
   e:   9203            str     r2, [sp, #12]
  10:   9302            str     r3, [sp, #8]
  12:   e7ff            b.n     14 <lmul_test::do_a_mul::hb8c09fc6501fea17+0x14>
  14:   9804            ldr     r0, [sp, #16]
  16:   9006            str     r0, [sp, #24]
  18:   9805            ldr     r0, [sp, #20]
  1a:   9007            str     r0, [sp, #28]
  1c:   9906            ldr     r1, [sp, #24]
  1e:   17c2            asrs    r2, r0, #31
  20:   17cb            asrs    r3, r1, #31
  22:   9001            str     r0, [sp, #4]
  24:   4608            mov     r0, r1
  26:   4611            mov     r1, r2
  28:   9a01            ldr     r2, [sp, #4]
  2a:   f7ff fffe       bl      0 <__aeabi_lmul>
  ...

According the the ARM ABI Documentation (Section 5.1.1.1), passing a 64bit value as an argument should store them in registers r0 and r1 or r2 and r3 as a pair. With the low bits being stored in r0/2 and the high bits stored in r1/3.

Looking at the dissasembled code, the first argument, which was initially stored in r0 gets its low and high bits split into r0 and r3 respectively, not the expected r0 and r1. The second argument also gets split into r2 and r1 for its low and high bits. So it appears that the high bits for the two values have been swapped.

Because the values are sign extended from 32 bits, this is only an issue if the signs of the two arguments differ. If they're the same the high bit registers will happen to be the same. But when the signs differ the function returns a wrong result in the r1 register. This causes a panic if overflow checking is enabled.

I've not noticed this issue anywhere else, but it could possibly be hidden around in some of the other compiler builtins.

I've created a sample repo here that shows off the bug. You'll need an ARM toolchain to actually compile it down to a binary, but you should still be able to cross compile it into a static library. (the command I was using was xargo build --target=thumbv6m-none-eabi)

Version info:

lmul-test|master ⇒ rustc --version --verbose 
rustc 1.16.0-nightly (47c8d9fdc 2017-01-08)
binary: rustc
commit-hash: 47c8d9fdcf2e6502cf4ca7d7f059fdc1a2810afa
commit-date: 2017-01-08
host: x86_64-apple-darwin
release: 1.16.0-nightly
LLVM version: 3.9

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.O-ArmTarget: 32-bit Arm processors (armv6, armv7, thumb...), including 64-bit Arm in AArch32 stateP-lowLow priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions