Search code examples
delphiassemblyoptimizationdelphi-10.2-tokyo

Why is there two sequential move to EAX under optimization build?


I looked at the ASM code of a release build with all optimizations turned on, and here is one of the inlined function I came across:

0061F854 mov eax,[$00630bec]
0061F859 mov eax,[$00630e3c]
0061F85E mov edx,$00000001
0061F863 mov eax,[eax+edx*4]
0061F866 cmp byte ptr [eax],$01
0061F869 jnz $0061fa83

The code is pretty easy to understand, it builds an offset (1) into a table, compares the byte value from it to 1 and do a jump if NZ. I know the pointer to my table is stored in $00630e3c, but I have no idea where $00630bec is coming from.

Why is there two move to eax one after the other? Isn't the first one overwritten by the second one? Can this be a cache optimization thing or am I missing something unbelievably obvious/obscure?

The Delphi code for the above ASM is as follow:

if( TGameSignals.IsSet( EmitParticleSignal ) = True ) then [...]

IsSet() is an inlined class function and calls the inlined IsSet() function of TSignalManager:

class function TGameSignals.IsSet(Signal: PBucketSignal): Boolean;
begin
  Result := FSignalManagerInstance.IsSet( Signal );
end;

The final IsSet of the signal manager is as such:

function TSignalManagerInstance.IsSet( Signal: PBucketSignal ): Boolean;
begin
  Result := Signal.Pending;
end;

Solution

  • My best guess would be that $00630bec is a reference to the class TGameSignals. You can check it by doing

    ShowMessage(IntToHex(NativeInt(TGameSignals), 8))
    

    The pre-optimisation code was probably something like this

    0061F854 mov eax,[$00630bec] //Move reference to class TGameSignals in EAX
    0061F859 mov eax,[eax + $250] //Move Reference to FSignalManagerInstance at offset $250 in class TGameSignals in EAX
    

    the compiler optimised [eax + $250] to [$00630e3c], but didn't realize the previous MOV wasn't required anymore.

    I'm not an expert in codegen, so take it with a grain of salt...

    On a side note, in delphi, we usually write

    if TGameSignals.IsSet( EmitParticleSignal ) then
    

    As it's possible for the following IF to be true

    var vBool : Boolean
    [...]
    vBool := Boolean(10);
    if vBool and (vBool <> True) then
    

    Granted, this is not good practice, but no point in comparing to TRUE either.

    EDIT: As pointed out by Ped7g, I was wrong. The instruction is

    0061F854 mov eax,[$00630bec] 
    

    and not

    0061F854 mov eax,$00630bec
    

    So what I wrote didn't really make sense... The first MOV instruction serve to pass the "self" reference for the call to TGameSignals.IsSet. Now, if the function wasn't inline, it would look like this :

    mov eax,[$00630bec]
    call TGameSignals.IsSet
    

    and then

    *TGameSignals.IsSet
    mov eax,[$00630e3c]
    [...]
    

    The first mov is still pointless, since "Self" isn't used in TGameSignals.IsSet but it is still required to pass "self" to the function. When the routine get inlined, it looks a lot more silly, indeed.

    Like mentioned by Arnaud Bouchez, making TGameSignals.IsSet static remove the implicit Self parameter and thus, remove the first MOV operation.