ARM Compiler does strange things in Release mode...
Originally from ticket #7713.
I have the following very simple CDC/VCP USB device code :
int main(void)
{
Set_System();
Set_USBClock();
USB_Interrupts_Config();
USB_Init();
while (1)
{
if (bDeviceState == CONFIGURED)
{
while (packet_sent != 1);
CDC_Send_DATA ((unsigned char*)"This is a test!\n\r",17);
}
}
}
The code works great in debug mode! :) This is what it looks like and it looks fine to me!
--- main.c -- 53 -------------------------------------------
* Output : None.
* Return : None.
*******************************************************************************/
int main(void)
{
B580 push {r7, lr}
AF00 add r7, sp, #0
--- main.c -- 58 -------------------------------------------
Set_System();
F000F820 bl 0x080002F4 <Set_System>
--- main.c -- 59 -------------------------------------------
Set_USBClock();
F000F846 bl 0x08000344 <Set_USBClock>
--- main.c -- 60 -------------------------------------------
USB_Interrupts_Config();
F000F870 bl 0x0800039C <USB_Interrupts_Config>
--- main.c -- 61 -------------------------------------------
USB_Init();
F002FB02 bl 0x080028C4 <USB_Init>
E000 b 0x080002C4 <main+0x18>
--- main.c -- 67 -------------------------------------------
while (packet_sent != 1);
CDC_Send_DATA ((unsigned char*)"This is a test!\n\r",17);
}
}
BF00 nop
--- main.c -- 72 -------------------------------------------
if (bDeviceState == CONFIGURED)
F240134C movw r3, #0x14C
F2C20300 movt r3, #0x2000
681B ldr r3, [r3]
2B05 cmp r3, #5
D1F7 bne 0x080002C2 <main+0x16>
--- main.c -- 66 -------------------------------------------
{
while (packet_sent != 1);
BF00 nop
--- main.c -- 69 -------------------------------------------
while (packet_sent != 1);
F2400300 movw r3, #0
F2C20300 movt r3, #0x2000
681B ldr r3, [r3]
2B01 cmp r3, #1
D1F8 bne 0x080002D4 <main+0x28>
--- main.c -- 69 -------------------------------------------
CDC_Send_DATA ((unsigned char*)"This is a test!\n\r",17);
F24460E0 movw r0, #0x46E0
F6C00000 movt r0, #0x800
F04F0111 mov.w r1, #17
F000F90F bl 0x08000510 <CDC_Send_DATA>
--- main.c -- 70 -------------------------------------------
}
}
E7E6 b 0x080002C2 <main+0x16>
However, as soon as I proceed to run the code in Thumb Release mode, this is what I see and it's quite alarming! It jumps right past the compare instructions straight to the end of the super loop! Not only that, but continues to jump to the end of the super loop forever! Is your optimizer doing this? How do we get around this ?
<main>
<__init_load_end__>
B5F8 push {r3-r7, lr}
F000F87D bl 0x080003AC <Set_System>
F000F895 bl 0x080003E0 <Set_USBClock>
F000F8B5 bl 0x08000424 <USB_Interrupts_Config>
F001FCCD bl 0x08001C58 <USB_Init>
F2401450 movw r4, #0x150
F2C20400 movt r4, #0x2000
F2400700 movw r7, #0
F2C20700 movt r7, #0x2000
F2434684 movw r6, #0x3484
F6C00600 movt r6, #0x800
F04F0511 mov.w r5, #17
6823 ldr r3, [r4]
2B05 cmp r3, #5
D1FC bne 0x080002DA <main+0x2E>
6838 ldr r0, [r7]
2801 cmp r0, #1
D104 bne 0x080002F0 <main+0x44> (Jumps right out of the logic to the end of supper loop! Why ???)
4630 mov r0, r6
4629 mov r1, r5
F000F8EF bl 0x080004CC <CDC_Send_DATA>
E7F4 b 0x080002DA <main+0x2E>
E7FE b 0x080002F0 <main+0x44> (Jumps here forever! Until I kill the pesky thing!)
This is still a personal project, right?
The issue I suspect is that you have not marked those variables volatile. If they are not marked volatile, you are telling the compiler that they won't ever change behind its back. So, if a test is true once, it's true forever, even if you change the value in an ISR (which you should not, because it's not volatile...)
Yes, this is a personal test! What I don't understand, is what is it
with the jumps from the loop? I don't think you explained that part
well Paul! I can understand the part with the volatiles, but why is it
changing the branching aiming for the end of the loop? I would figure
that the test on the variables would be the same check as it is in
Debug mode. What's the difference ? Is the fact that the variables are
changed somewhere else during an ISR call causing the compiler to
raise a flag and cause the branch decisions based on those
non-volatiles to aim towards the end of the program? Or is it a
safeguard that you put into the licensing that causes branching to go
there because someone is using a USB stack and you feel it is
violating your licensing agreement? I find your first question a bit
of a hint and don't quite understand why the behaviors are different
none the less! Enlighten me with a better explanation...Why does the
optimizer kill the code flow?
I'm going to move this to a forum to help others that might be similarly interested in my response as it'll be a little more detailed. I've said how to fix it (as it is a user bug) but you require more explanation. I'm not prepared to do that privately and engage in a long debate about it, so it goes public.
-
The solution is quite simple: use volatile correctly on variables that are likely, or will, change behind the compiler's back without it knowing about them!
Let's take a very simple function:
static int x; void foo(void) { while (x == 1) { ++x; } }There's a loop in there, correct? No, you're wrong, there isn't, and the compiler knows it because it can reason about the code it sees. If it could not reason about the code that it sees, it would not be able to optimize it. Customers want their code optimized, but they tell the compiler all sorts of lies about the way their code runs. Here's the output:
foo: ldr r3, .L8 ldr r3, [r3] cmp r3, #1 bne .L5 movs r2, #2 ldr r3, .L8 str r2, [r3] .L5: bx lr .L9:
No loop. The compiler knows that if it enters and sees that x is 1, then on exit x will be 2. If it enters and sees that x is not one, x is unchanged. And that is what the compiler coded into the function.
Let's change this a bit more. Rather than a static x, let's do something that is unexpected. Let's move the declaration of x into the function and see if we get a loop.
void foo(void) { int x; while (x == 1) { ++x; } }Now, what do you suppose the compiler produces? Well, ithis:
foo: bx lr
No loop! Nothing! What's that? Well, it's because x doesn't have a defined value on entry, so it could be one, or it could be 925. So, the compiler says "well, I can do anything here, so le me do what generates the fastest/smallest/best code" and it throws everything away. There are many, many more examples of this. The compiler will reason about your code, and can do so very well, and in the end it generates what it feels you've asked for. If you have not applied volatile correctly.
Please sign in to leave a comment.
Comments
1 comment