Search code examples
delphidelphi-7inline-assemblybasm

Delphi assembler: understanding the Result register


I'm messing around with ASM in Delphi. From my understanding, EAX holds Result. In the following, I have to put RET at the end, otherwise Result is not correct (it is correct if the input is 0). What am I doing wrong, or should I say, what don't I understand about this?

function MSb(const Val: Integer): Integer;
label
  Go;
asm
  CMP       EAX, 0
  JNZ       Go
  MOV       EAX, -1
  RET
Go:
  BSR       EBX, EAX
  MOV       EAX, EBX
  RET
end;

If I say the following:

  MOV       Result, EBX

Then I get the following compilation:

  MOV [EPB-$04], EBX
  MOV EAX, [EPB-$04]

However, my code above has the following postscript:

  MOV       EAX, EDX
  RET

Solution

  • At least without optimization enabled, your function has a pre-amble and a post-amble. Have a look at it:

    Project46.dpr.13: asm
    0041A1F4 55               push ebp
    0041A1F5 8BEC             mov ebp,esp
    0041A1F7 83C4F8           add esp,-$08
    0041A1FA 8945F8           mov [ebp-$08],eax
    Project46.dpr.14: CMP       EAX, 0
    0041A1FD 83F800           cmp eax,$00
    Project46.dpr.15: JNZ       Go
    0041A200 7506             jnz $0041a208
    Project46.dpr.16: MOV       EAX, -1
    0041A202 B8FFFFFFFF       mov eax,$ffffffff
    Project46.dpr.17: RET
    0041A207 C3               ret 
    Project46.dpr.19: BSR       EBX, EAX
    0041A208 0FBDD8           bsr ebx,eax
    Project46.dpr.20: MOV       EAX, EBX
    0041A20B 89D8             mov eax,ebx
    Project46.dpr.21: RET
    0041A20D C3               ret 
    Project46.dpr.22: end;
    0041A20E 8B45FC           mov eax,[ebp-$04]
    0041A211 59               pop ecx
    0041A212 59               pop ecx
    0041A213 5D               pop ebp
    0041A214 C3               ret 
    

    So the pre-amble sets up the stack frame. It saves away the ebp register, and modifies both the ebp and esp registers. Notice also the post-amble. You do need to execute that code to restore the saved registers.

    The usual way to deal with this is to jump to the end of the function instead of using ret. So write your code like this:

    function MSb(const Val: Integer): Integer;
    asm
      CMP       EAX, 0
      JNZ       @@go
      MOV       EAX, -1
      JMP       @@exit
    @@go:
      BSR       EBX, EAX
      MOV       EAX, EBX
    @@exit:
    end;
    

    This way you ensure that the post-amble is executed. You really should get into the habit of writing the code this way to ensure that any pre-amble is executed.


    Now, beyond that I suspect that the problem you mention in the question actually relates to a compiler bug relating to your use of a Pascal label rather than an asm label. Well, perhaps it is a compiler bug, but perhaps it is just a mistake to use a Pascal label. Consider the following program:

    {$APPTYPE CONSOLE}
    
    function MSb(const Val: Integer): Integer;
    asm
      CMP       EAX, 0
      JNZ       @@Go
      MOV       EAX, -1
      JMP       @@exit
    @@Go:
      BSR       EBX, EAX
      MOV       EAX, EBX
    @@exit:
    end;
    
    function MSb2(const Val: Integer): Integer;
    label
      Go;
    asm
      CMP       EAX, 0
      JNZ       Go
      MOV       EAX, -1
      RET
    Go:
      BSR       EBX, EAX
      MOV       EAX, EBX
    end;
    
    begin
      Writeln(Msb(0));
      Writeln(Msb(1));
      Writeln(Msb2(0));
      Writeln(Msb2(1));
      Readln;
    end.
    

    The output when compiled with optimization is:

    -1
    0
    -1
    4
    

    So, what about that rather odd 4. Well, let's look at the assembled code for Msb2, which is essentially your code:

    004059E8 83F800           cmp eax,$00
    004059EB 7506             jnz $004059f3
    004059ED B8FFFFFFFF       mov eax,$ffffffff
    004059F2 C3               ret 
    004059F3 0FBDD8           bsr ebx,eax
    004059F6 89D8             mov eax,ebx
    004059F8 8BC2             mov eax,edx
    004059FA C3               ret 
    

    Why on earth is the value of edx, a volatile register whose value has not been assigned, being moved into eax just before the function returns. This is the problem that you are reporting. My guess is that the use of a Pascal label is confusing the assembler. Stick to asm labels.

    Here is the assembled code for Msb:

    004059D4 83F800           cmp eax,$00
    004059D7 7506             jnz $004059df
    004059D9 B8FFFFFFFF       mov eax,$ffffffff
    004059DE C3               ret 
    004059DF 0FBDD8           bsr ebx,eax
    004059E2 89D8             mov eax,ebx
    004059E4 C3               ret 
    

    That's more like it! Notice how the compiler knows that there is no post-able here, and replaces the jmp @@exit with a straight ret.