为什么在优化构建下有两次连续移动到 EAX?

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

我查看了启用了所有优化的发布版本的 ASM 代码,这是我遇到的内联函数之一:

0061F854 mov eax,[[=10=]630bec]
0061F859 mov eax,[[=10=]630e3c]
0061F85E mov edx,[=10=]000001
0061F863 mov eax,[eax+edx*4]
0061F866 cmp byte ptr [eax],
0061F869 jnz [=10=]61fa83

代码很容易理解,它将偏移量 (1) 构建到 table,将其字节值与 1 进行比较,如果 NZ 则跳转。我知道指向我的 table 的指针存储在 $00630e3c 中,但我不知道 $00630bec 是从哪里来的。

为什么要两步走一个接一个?第一个不是被第二个覆盖了吗?这可能是缓存优化问题还是我遗漏了令人难以置信的东西 obvious/obscure?

上述ASM的Delphi代码如下:

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

IsSet()是一个内联的class函数,调用TSignalManager的内联IsSet()函数:

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

信号管理器最终的IsSet是这样的:

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

我最好的猜测是 $00630bec 是对 class TGameSignals 的引用。您可以通过

来检查它
ShowMessage(IntToHex(NativeInt(TGameSignals), 8))

优化前的代码大概是这样的

0061F854 mov eax,[[=11=]630bec] //Move reference to class TGameSignals in EAX
0061F859 mov eax,[eax + 0] //Move Reference to FSignalManagerInstance at offset 0 in class TGameSignals in EAX

编译器将[eax + 0]优化为[[=19=]630e3c],但没有意识到不再需要之前的MOV。

我不是 codegen 方面的专家,所以对它持保留态度...

旁注,在delphi中,我们通常写

if TGameSignals.IsSet( EmitParticleSignal ) then

因为下面的 IF 可能为真

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

当然,这不是好的做法,但与 TRUE 相比也没有意义。

编辑:正如 Ped7g 所指出的,我错了。指令是

0061F854 mov eax,[[=14=]630bec] 

而不是

0061F854 mov eax,[=15=]630bec

所以我写的东西真的没有意义...... 第一条 MOV 指令用于传递 "self" 引用以调用 TGameSignals.IsSet。现在,如果函数不是内联的,它看起来像这样:

mov eax,[[=16=]630bec]
call TGameSignals.IsSet

然后

*TGameSignals.IsSet
mov eax,[[=17=]630e3c]
[...]

第一个 mov 仍然没有意义,因为 TGameSignals.IsSet 中没有使用 "Self" 但仍然需要将 "self" 传递给函数。当例程被内联时,它看起来确实更傻了。

正如 Arnaud Bouchez 所提到的,使 TGameSignals.IsSet static 删除隐式的 Self 参数,从而删除第一个 MOV 操作。