I came across another interesting compiler bug today. I’m not going to go into too much detail about it, but the problem code is this:
1 2 3 4 5 6 7 8 9 10 |
|
To break that down a bit, the function is doing this:
- Initialise a couple of variables and set them to 0.
1 2 |
|
- Then perform some inline assembly using the variables. This just puts 0 into the register that will hold our ‘a’ variable, then loads the value of ‘b’ into ‘a’, then sets ‘b’ to 0.
1 2 3 4 5 6 |
|
This is of course a contrived example, but it illustrates the bug. The output assembly from LLVM-GCC or clang is (for ARM architecture):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
The interesting bit is the inline assembly. You’ll notice that it’s doing something very stupid. It’s choosing the same register for both operands (it’s choosing r0). This is completely wrong, and will lead to a runtime crash in this case due to the dereference of 0.
I did a bit of hunting and it appears to be a problem in generating the LLVM bytecode as the problem manifests itself before the LLVM bytecode is compiled down into instructions, like so:
1 2 3 4 5 6 7 8 9 |
|
You can see here that the ‘%a’ (i.e. variable ‘a’) is never referenced, only ‘%b’ (i.e. variable ‘a’). This is not what we’d expect at all given we’re referencing both variables in the code.
I found this quite interesting :–).
Update: Not a bug!
I’ve actually found out that this isn’t a bug! That’s good news, right? It’s quite a subtle thing, but the heart of the problem can be explained after understanding the modifiers to operands on inline assembly. The problem is that we’re not specifying that ‘a’ is clobbered early. In the assembly, we’re writing to it before reading we’ve finished using all input operands (only ‘b’ in this case) so we’re meant to mark it like that. It’s just luck that GCC does the right thing – Apple’s LLVM is doing the right thing and using less registers!
So the correct code is this:
1 2 3 4 5 6 7 8 9 10 |
|
Which results in the following assembly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
And the following LLVM:
1 2 3 4 5 6 7 8 9 |
|