title: An empty cycle in C. Does the compiler generate lots of unnecessary code or did I miss something?
body: <p>I am new to AVR assembly language and decided to look inside the code of a dumb delay function written in C to see how long an empty cycle with long arithmetics could take.  </p>
  
  <p>The delay function is as follows:</p>
  
  <pre><code>void delay(uint32_t cycles) {
      for (volatile uint32_t i = 0; i &lt; cycles; i++) {}
  }
  </code></pre>
  
  <p>I disassembled it with <code>objdump</code> and, I think, got some strange results (see four questions in the comments):</p>
  
  <pre><code>00000080 &lt;delay&gt;:
  void delay (uint32_t cycles) {                  
  ; `cycles` is stored in r22..r25
    80:   cf 93           push    r28
    82:   df 93           push    r29
  ; First one: why does the compiler rcall the next position relative to the following
  ; two instructions? Some stack management?
    84:   00 d0           rcall   .+0             ; 0x86 &lt;delay+0x6&gt;
    86:   00 d0           rcall   .+0             ; 0x88 &lt;delay+0x8&gt;
    88:   cd b7           in      r28, 0x3d       ; 61
    8a:   de b7           in      r29, 0x3e       ; 62
    8c:   ab 01           movw    r20, r22
    8e:   bc 01           movw    r22, r24
  ; Now `cycles` is in r20..r23
      for (volatile uint32_t i = 0; i &lt; cycles; i++) {}
  ; r1 was earlier initialized with zero by `eor r1, r1`
  ; `i` is in r24..r27
    90:   19 82           std     Y+1, r1 ; 0x01
    92:   1a 82           std     Y+2, r1 ; 0x02
    94:   1b 82           std     Y+3, r1 ; 0x03
    96:   1c 82           std     Y+4, r1 ; 0x04
    98:   89 81           ldd     r24, Y+1        ; 0x01
    9a:   9a 81           ldd     r25, Y+2        ; 0x02
    9c:   ab 81           ldd     r26, Y+3        ; 0x03
    9e:   bc 81           ldd     r27, Y+4        ; 0x04
    a0:   84 17           cp      r24, r20
    a2:   95 07           cpc     r25, r21
    a4:   a6 07           cpc     r26, r22
    a6:   b7 07           cpc     r27, r23
    a8:   a0 f4           brcc    .+40            ; 0xd2 &lt;delay+0x52&gt;, to location A
  ; location B:
  ; Third (yes, before the second) one: why does it load the registers each time after
  ; comparing the counter with the limit if `cp`, `cpc` do not change the registers?
    aa:   89 81           ldd     r24, Y+1        ; 0x01
    ac:   9a 81           ldd     r25, Y+2        ; 0x02
    ae:   ab 81           ldd     r26, Y+3        ; 0x03
    b0:   bc 81           ldd     r27, Y+4        ; 0x04
    b2:   01 96           adiw    r24, 0x01       ; 1
    b4:   a1 1d           adc     r26, r1
    b6:   b1 1d           adc     r27, r1
  ; Second one: why does it store and load the same registers with unchanged values?
  ; If it needs to store the registers, why does it load anyway? Does `std` change the
  ; source registers?
    b8:   89 83           std     Y+1, r24        ; 0x01
    ba:   9a 83           std     Y+2, r25        ; 0x02
    bc:   ab 83           std     Y+3, r26        ; 0x03
    be:   bc 83           std     Y+4, r27        ; 0x04
    c0:   89 81           ldd     r24, Y+1        ; 0x01
    c2:   9a 81           ldd     r25, Y+2        ; 0x02
    c4:   ab 81           ldd     r26, Y+3        ; 0x03
    c6:   bc 81           ldd     r27, Y+4        ; 0x04
    c8:   84 17           cp      r24, r20
    ca:   95 07           cpc     r25, r21
    cc:   a6 07           cpc     r26, r22
    ce:   b7 07           cpc     r27, r23
    d0:   60 f3           brcs    .-40            ; 0xaa &lt;delay+0x2a&gt;, to location B
  }
  ; Location A:
  ; Finally, fourth one: so, under my first question it issued an `rcall` twice and now 
  ; just pops the return addresses to nowhere? Now the `rcall`s are double-strange
    d2:   0f 90           pop     r0
    d4:   0f 90           pop     r0
    d6:   0f 90           pop     r0
    d8:   0f 90           pop     r0
    da:   df 91           pop     r29
    dc:   cf 91           pop     r28
    de:   08 95           ret
  </code></pre>
  
  <p>So after all, why does it need all those actions?</p>
  
  <h3>UPD</h3>
  
  <p>Full code:</p>
  
  <pre><code>#include &lt;avr/io.h&gt;
  
  void delay (uint32_t cycles)
  {
      for (volatile uint32_t i = 0; i &lt; cycles; i++) {}
  }
  
  int main(void)
  {
      DDRD |= 1 &lt;&lt; DDD2 | 1 &lt;&lt; DDD3 | 1 &lt;&lt; DDD4 | 1 &lt;&lt; DDD5;
      PORTD |= 1 &lt;&lt; PORTD2 | 1 &lt;&lt; PORTD4;
      while (1) 
      {
          const uint32_t d = 1000000;
          delay(d);
          PORTD ^= 1 &lt;&lt; PORTD2 | 1 &lt;&lt; PORTD3;
          delay(d);
          PORTD ^= 1 &lt;&lt; PORTD4 | 1 &lt;&lt; PORTD5;
          delay(d);
          PORTD ^= 1 &lt;&lt; PORTD3 | 1 &lt;&lt; PORTD2;
          delay(d);
          PORTD ^= 1 &lt;&lt; PORTD5 | 1 &lt;&lt; PORTD4;
      }
  }
  </code></pre>
  
  <p>Compiler: <code>gcc version 5.4.0 (AVR_8_bit_GNU_Toolchain_3.6.0_1734)</code></p>
  
  <p>Build command:</p>
  
  <pre><code>avr-gcc.exe  -x c -funsigned-char -funsigned-bitfields -DDEBUG  -I%inc_folder%  -O1 -ffunction-sections -fdata-sections -fpack-struct -fshort-enums -g2 -Wall -mmcu=atmega328p -B %atmega328p_folder% -c -std=gnu99 -MD -MP %sources, object files, etc%
  </code></pre>
  
  <p><strong>A reply to the cautions about the delay function:</strong></p>
  
  <p>Yes, I fully understand the possible problems with such approach to the delay function, namely the not-so-predictable timing and the risk of optimizing out the cycle. This is only a self-educational example to see what an empty cycle is compiled into</p>