Skip to content

Broken match statement when compiling for thumbv6-none-eabi (ARM cortex-m0) #42248

Closed
@alevy

Description

@alevy

After upgrading Tock to use a recent nightly, compilation for the NRF51-DK (a Cortex-M0 based board) results in unexpected assembly for a particular match statement.

The match statement looks like this:

impl kernel::Platform for Platform {
    fn with_driver<F, R>(&self, driver_num: usize, f: F) -> R
        where F: FnOnce(Option<&kernel::Driver>) -> R
    {
        match driver_num {
            0 => f(Some(self.console)),
            1 => f(Some(self.gpio)),
            3 => f(Some(self.timer)),
            8 => f(Some(self.led)),
            9 => f(Some(self.button)),
            14 => f(Some(self.rng)),
            17 => f(Some(self.aes)),
            36 => f(Some(self.temp)),
            _ => f(None),
        }
    }
}

The LLVM IR that (seems to) corresponds is:

bb49.i.i.i.i:                                     ; preds = %_ZN6kernel7process7Process18incr_syscall_count17h073864aec83cd68fE.exit
  %1192 = load volatile i32, i32* %1028, align 4
  switch i32 %1192, label %bb12.i.i.i1127 [
    i32 0, label %bb1.i.i.i.i.i
    i32 1, label %bb2.i.i.i.i.i
    i32 3, label %bb3.i.i.i.i.i
    i32 8, label %bb4.i57.i.i.i.i
    i32 9, label %bb5.i58.i.i.i.i
    i32 14, label %bb6.i.i.i.i.i
    i32 17, label %bb7.i.i.i.i.i
    i32 36, label %bb8.i.i.i.i.i
  ]

which is a fairly straight forward translation.

In general, the template for the expected output in assembly is something like:

;given r1 is `driver_num`, the variable we're matching against
lsls    r1, r1, #1
add     r1, pc
ldrh    r1, [r1, #4] ; load a half-word from the switch's offset table
lsls    r1, r1, #1 ; this half-word is actually 2x what it should be (idk why, just cause)
add     pc, r1 ; use the result as an offset from the current PC and jump there
                     ; (because we're executing a closure

The match statement seems to end up being broken up (makes sense since the matched values are sparse), with one of the cases (which is called at least when driver_num is 3) looking like this:

add     r1, pc, #16
lsls    r1, r1, #1
ldrh    r1, [r1, r1]
lsls    r1, r1, #1
add     pc, r1

This basically doesn't make any sense... it's grabbing a text segment address, multiplying it by two then loading a half-word from an address that's again multiplied by two (and at this point, completely outside the text segment so it could be anything really). In practice, that returns 0xffff, which after shifting left by one results in 0x1fffe. Finally, adding that to the current PC is way way way outside of the text segment and results in a hardware fault.

Notes

We can "fix" this in a variety of ways:

  • Commenting out the 17 valued branch (commenting out no other branch seems to do the trick)

  • Making the function inline(never)

  • Using opt-level 0 or 1

  • Compiling for better supported architectures by LLVM (e.g. thumbv7m-none-eabi) although those don't run on the same hardware so I can only confirm

Meta

This is using rustc 1.19.0-nightly (5b13bff52 2017-05-23)

Reproducing

Checkout commit d5565a227f8aa40e8c94efa9877a425003d02752 from helena-project/tock and:

$ make TOCK_BOARD=nrf51dk

the resulting binary will be boards/nrf51dk/target/thumbv6-none-eabi/release/nrf51dk, which you can read with objdump:

$ arm-none-eabi-objdump -d boards/nrf51dk/target/thumbv7-none-eabi/release/nrf51dk

Metadata

Metadata

Assignees

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-highHigh priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.regression-from-stable-to-nightlyPerformance or correctness regression from stable to nightly.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions