2

Here's code that performs some arithmetics.

int main(void) {
    int i = 3;
    i++;
    i+= 2;
    return 0;
}

I compiled it using 32-bit tcc with the following command

tcc -o hello.exe hello.c

I, then, disassembled it using IDA free edition, and after some time staring at the start of the instructions I realized that the main function that I was looking for is in a subroutine, and going there I see this:

sub_401000 proc near

var_4= dword ptr -4

push    ebp               
mov     ebp, esp           
sub     esp, 4            // allocate 4 bytes on the stack for var i
nop

mov     eax, 3            // i = 3 instructions
mov     [ebp+var_4], eax  // 

mov     eax, [ebp+var_4]  // i++ instructions
mov     ecx, eax          // 
inc     eax               // 
mov     [ebp+var_4], eax  //

mov     eax, [ebp+var_4]  // i+=2 instructions
add     eax, 2            //   
mov     [ebp+var_4], eax  //  

mov     eax, 0           
jmp     $+5              
leave
retn
sub_401000 endp

I've added my understanding of what's going on in comments on the right for the body of the method that I am interested in.

For example, incrementing the variable would involve moving a value onto a register and then operating on it. I would expect i++ to look something like

mov     eax, [ebp+var_4]
inc     eax             

But the actual instructions involved an extra move

mov     eax, [ebp+var_4]  
mov     ecx, eax         // <----- ?
inc     eax               

In the add instruction, the extra move isn't there. When I modified the code with a decrement operation, I see that extra move as well.

Is there some purpose for this move from eax to ecx ?

UPDATE:

I am still reading about registers, and from what I've read ecx is used as a counter, but from this code it isn't obvious what it's being used for, if anything.

3
  • possible duplicate of Why are values passed through useless copies? Jun 19, 2014 at 21:45
  • 3
    The counter feature of ecx is used with rep/loop instructions, which is certainly not the case here. I'd guess it's an artifact of postincrement, where the value of the expression is the unchanged value. When the compiler compiles the i++ expression, it puts the old value into ecx in case some outer expression needs it; without optimizations, it never realizes there is no outer expression. Would be interesting to know if the instruction disappears when you use ++i instead. Jun 20, 2014 at 7:03
  • @GuntramBlohm the instruction indeed does not appear if it were ++i instead.
    – MxLDevs
    Jun 20, 2014 at 14:43

2 Answers 2

3

TCC was written to be fast and simple without most optimizations. It compiles the program in only one pass and use a very limited number of registers (eax, ecx and edx, I think). So don't suppose for it to be efficient and don't surprise when it does something "stupidly".

On x86, three temporary registers are used. When more registers are needed, one register is spilled into a new temporary variable on the stack.

http://bellard.org/tcc/tcc-doc.html#SEC31

TCC generates code in a single pass, and does not perform most of the optimizations performed by other compilers such as GCC. TCC compiles every statement on its own, and at the end of each statement register values are written back to the stack and must be re-read even if the next line uses the values in registers (creating extraneous save/load pairs between statements). TCC uses only some of the available registers (e.g., on x86 it never uses ebx, esi, or edi because they need to be preserved across function calls).[4]

https://en.wikipedia.org/wiki/Tiny_C_Compiler#Compiled_program_performance

4

The comment of Guntram Blohm is right. The original value of i is stored into ecx for a possible later use.

ISO/IEC 9899:TC3 (C99) --- 6.5.2.4 Postfix increment and decrement operators:

The result of the postfix ++ operator is the value of the operand. After the result is obtained, the value of the operand is incremented. (That is, the value 1 of the appropriate type is added to it.) See the discussions of additive operators and compound assignment for information on constraints, types, and conversions and the effects of operations on pointers. The side effect of updating the stored value of the operand shall occur between the previous and the next sequence point.

The sequence points are described in Annex C of the document.

TCC performs only few optimizations, the code is generated in a single pass and every C statement is compiled on its own. It seems that the compiler is almost as simple as possible so it does not store any state during the compilation if it is not necessary.

When TCC encountered the sequence point before ++ it did not know about the ++ operation. Performing the side-effect of the ++ operator later after evaluating the ++ operation would mean to store a state information. It seems that the author of TCC selected the simplest approach - to perform the side-effect together with the evaluation of the ++ operator.

The original value of the i variable (before incrementing) can possibly be used again in the same expression so it is saved to the ecx register and the result of the postfix increment operation is stored to the variable on the stack immediately (mov [ebp+var_4], eax). It does not matter that the value in ecx is not used later. This is the disadvantage due to the simplicity of TCC. For example you can notice that the code loads the value of i from stack to eax even if the value is in eax already.

Example of code which additionally uses the original value of i:

j = i++ + i * 3;

The assembly code:

13: 8b 45 fc        mov eax, DWORD PTR [rbp-0x4]    // load i to eax
16: 48 89 c1        mov ecx, eax                    // store the original value of i for later use
19: 83 c0 01        add eax, 0x1                    // increment the value (the side effect of ++)
1c: 89 45 fc        mov DWORD PTR [rbp-0x4], eax    // store it to i - everything between "=" and "+" is done at this point including the side-effect of i++
1f: 8b 45 fc        mov eax, DWORD PTR [rbp-0x4]    // load i to eax (Now it is the incremented value.)
22: ba 03 00 00 00  mov edx, 0x3                    // load value 3 for the multiplication
27: 0f af c2        imul    eax, edx                // multiply
2a: 01 c1           add ecx, eax                    // add the original value of i (the result of i++)
2c: 89 4d f8        mov DWORD PTR [rbp-0x8], ecx    // store the result to j

At 1f the incremented value of i is being used but it is also possible to use the original value of i there and still be in adherence with C99 because the sequence point after i++ was not encountered yet. In this case the sequence points are right before and after the statement.

2
  • A recursive-descent parser wouldn't need to do anything fancy to have a "generate code for expression whose result is ignored", "generate code for expression and branch to X if zero and Y if zero (using a dummy value for X or Y to indicate fall-through)", and have those fall through to "generate code for expression whose result is used", or a combination of "generate code for expression whose result is used" followed by "test result against zero and branch to X or Y". Separating things out in that fashion would be the easiest way to efficiently handle...
    – supercat
    Jun 8 at 16:44
  • ...things like comparison operations and short-circuit evaluation, so that if (a==b && c==d) doesn't get processed with code equivalent to if ( (((a==b)?1:0) ? ((c==d)?1:0) : 0) != 0). Having the "evaluate expression whose result is ignored" look for expressions of the form "++x"., "x++", "--x", and "x--" and special-case those would be a simple optimization with a large payoff.
    – supercat
    Jun 8 at 16:49

Your Answer

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

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