9
\$\begingroup\$

I can not understand why GCC compiler cutting out part of my code while it preserve absolutely the same one in the neighborhood?

The C code:

#define setb_SYNCO do{(PORTA|= (1<<0));} while(0);

ISR(INT0_vect){
    unsigned char i;

    i = 10;
    while(i>0)i--;   // first pause - omitted

    setb_SYNCO;
    setb_GATE;
    i=30;
    clrb_SYNCO;
    while(i>0)i--;  // second pause - preserved
    clrb_GATE;
}

The corresponding part of LSS (assembler file, created by the compiler):

ISR(INT0_vect){
  a4:   1f 92           push    r1
  a6:   0f 92           push    r0
  a8:   0f b6           in  r0, 0x3f    ; 63
  aa:   0f 92           push    r0
  ac:   11 24           eor r1, r1
  ae:   8f 93           push    r24
    unsigned char i;

    i = 10;
    while(i>0)i--;

    setb_SYNCO;
  b0:   d8 9a           sbi 0x1b, 0 ; 27
    setb_GATE;
  b2:   d9 9a           sbi 0x1b, 1 ; 27
    i=30;
    clrb_SYNCO;
  b4:   d8 98           cbi 0x1b, 0 ; 27
  b6:   8e e1           ldi r24, 0x1E   ; 30
  b8:   81 50           subi    r24, 0x01   ; 1
    while(i>0)i--;
  ba:   f1 f7           brne    .-4         ; 0xb8 <__vector_1+0x14>
    clrb_GATE;
  bc:   d9 98           cbi 0x1b, 1 ; 27
}
  be:   8f 91           pop r24
  c0:   0f 90           pop r0
  c2:   0f be           out 0x3f, r0    ; 63
  c4:   0f 90           pop r0
  c6:   1f 90           pop r1
  c8:   18 95           reti

I could assume that compiler figure out that such code is dummy and cuts it out but why is it preserve the same one in the end of the code?

Is there any compiler instructions to prevent from such optimization?

\$\endgroup\$
13
  • 1
    \$\begingroup\$ You can also tell the compiler to not optimize a single function, maybe it's worth to try this method with your ISR. See this question on stackoverflow. \$\endgroup\$ Commented Jan 26, 2015 at 11:43
  • 2
    \$\begingroup\$ Hey Roman, I added the "c" tag to your question removing atmega. I had to remove one tag since there's a limit (five), and when asking a code related question adding the language name as a tag is great because all code (Q&As) gets highlighted. \$\endgroup\$ Commented Jan 26, 2015 at 13:41
  • 5
    \$\begingroup\$ Generally speaking, higher level languages (like C) are explicitly designed to not be bound to a 1:1 relationship with their resulting assembly. If you need to count instructions to get the timing right, you always have to rely on assembly (as some of the answers did). The whole point of high level languages is that the compiler has some freedom to help make your code faster-stronger-better than before. Details like register allocations and branch predictions are much better left to the compiler... except in times like this where you, the programmer, know exactly the instructions you want. \$\endgroup\$
    – Cort Ammon
    Commented Jan 26, 2015 at 15:13
  • 5
    \$\begingroup\$ The better question is, why isn't GCC optimizing away both of those loops? \$\endgroup\$ Commented Jan 26, 2015 at 17:01
  • 5
    \$\begingroup\$ Gcc first unrolls the loop and only then notices that the corresponding code is useless. With a loop size of 30, unrolling would be foolish and gcc doesn't do it. At a higher optimization level both are optimized away. \$\endgroup\$ Commented Jan 26, 2015 at 18:46

4 Answers 4

9
\$\begingroup\$

Since in one comment you state that "each CPU tick is worthy" I suggest using some inline assembly to make your delays loop just as you want. This solution is superior to the various volatile or -O0 because it makes clear what your intent is.

unsigned char i = 10;
__asm__ volatile ( "loop: subi    %0, 0x01\n\t"
                   "      brne    loop"
                   : "+rm" (i)
                   : /* no inputs */
                   : /* no dirty registers to decleare*/);

That should do the trick. The volatile thing is there to tell the compiler "I know this does not do anything, just keep it and trust me". The three asm "statements" are quite self explanatory, you can use any register instead of r24, I believe the compiler likes lower registers so you might want to use a high one. After the first : you should list output (read and write) c variables, and there's none, after the second : you should list input (ronly) c variables, again, there is none, and the third parameter is a comma separated list of modified registers, in this case r24. I am not sure if you should include also the status register since the ZERO flag changes of course, I did not include it.

edit edited answer as OP requested. Some notes.

The "+rm" before (i) means that you are letting the compiler decide to place i in memory or in a register. That's a good thing in most cases since the compiler can optimize better if it's free. In your case I believe you want to keep only the r constraint to force i to be a register.

\$\endgroup\$
2
  • \$\begingroup\$ Looks like this is a thing I really need. But could you modify your answer to accept any c variable instead of literal 10 I mentioned in the original answer? I'm trying to read the GCC manuals regarding the proper use of asm construction but it's a bit obscured for me now. I'd be very appreciate! \$\endgroup\$ Commented Jan 26, 2015 at 16:45
  • 1
    \$\begingroup\$ @RomanMatveev edited as you requested \$\endgroup\$ Commented Jan 26, 2015 at 23:48
13
\$\begingroup\$

You could try making the loop actually do something. As it stands the compiler is quite rightly saying "This loop is doing nothing - I'll get rid of it".

So you could try a construct I use frequently:

int i;
for (i = 0; i < 10; i++) {
    asm volatile ("nop");
}

Note: not all targets for the gcc compiler use the same inline assembly syntax - you may need to tweak it for your target.

\$\endgroup\$
3
  • \$\begingroup\$ Your solution seems much more elegant than mine... maybe mine is better when PRECISE cycle count is requires? I mean, the whole for thing is not guaranteed to be compiled in a certain way, is it? \$\endgroup\$ Commented Jan 26, 2015 at 12:10
  • 8
    \$\begingroup\$ The simple fact it's using C means you can't guarantee cycles. If you need precise cycle counting then ASM is the only way to go. I mean, you get different timing with the C loop if you have -funroll-loops enabled than if you don't, etc. \$\endgroup\$
    – Majenko
    Commented Jan 26, 2015 at 13:35
  • \$\begingroup\$ Yeah that's what I thought. When doing HW delays with high enough i values (100 or more) I guess your solution yields virtually the same results while enhancing readability. \$\endgroup\$ Commented Jan 26, 2015 at 13:37
6
\$\begingroup\$

Yes you could assume that. If you declare variable i as volatile you tell the compiler not to optimise on i.

\$\endgroup\$
7
  • 1
    \$\begingroup\$ That's not entirely true in my opinion. \$\endgroup\$ Commented Jan 26, 2015 at 11:42
  • 1
    \$\begingroup\$ @VladimirCravero, what do you mean by saying that? Could you make more clear? \$\endgroup\$ Commented Jan 26, 2015 at 11:43
  • 2
    \$\begingroup\$ What I meant is that I would not be so sure about what a compiler does. Declaring a variable volatile tells the compiler that it might change somewhere else so it really should make that while. \$\endgroup\$ Commented Jan 26, 2015 at 11:46
  • 1
    \$\begingroup\$ @Roman Matveev register unsigned char volatile i __asm__("r1"); maybe? \$\endgroup\$
    – a3f
    Commented Jan 26, 2015 at 16:47
  • 2
    \$\begingroup\$ Declaring i as volatile solves everything. This is guaranteed by the C standard 5.1.2.3. A conforming compiler must not optimize away those loops if i is volatile. Luckily, GCC is a conforming compiler. There are unfortunately many would-be C compilers that don't conform to the standard, but that's irrelevant for this particular question. There is absolutely no need for inline assembler. \$\endgroup\$
    – Lundin
    Commented Jan 28, 2015 at 15:14
1
\$\begingroup\$

After the first loop i is a constant. The initialization of i and the loop do nothing but produce a constant value. Nothing in the standard specifies that this loop must be compiled as-is. The standard does not say anything about timing either. The compiled code must behave as if the loop was present and it does. You can't reliably tell that this optimization was performed under the standard (timing does not count).

The second loop should be deleted as well. I consider it to be a bug (or a missing optimization) that it is not. After the loop i is constant zero. The code should be replaced with setting i to zero.

I think GCC keeps i around purely for the reason that you perform an (opaque) port access might affect i.

Use

asm volatile ("nop");

to trick GCC into believing that the loop does something.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.