在64位内嵌汇编中访问Delphi类字段

3

我正在尝试将 Delphi TBits.GetBit 转换为 64 位版本的内联汇编。VCL 源代码如下:

function TBits.GetBit(Index: Integer): Boolean;
{$IFNDEF X86ASM}
var
  LRelInt: PInteger;
  LMask: Integer;
begin
  if (Index >= FSize) or (Index < 0) then
    Error;

  { Calculate the address of the related integer }
  LRelInt := FBits;
  Inc(LRelInt, Index div BitsPerInt);

  { Generate the mask }
  LMask := (1 shl (Index mod BitsPerInt));
  Result := (LRelInt^ and LMask) <> 0;
end;
{$ELSE X86ASM}
asm
    CMP     Index,[EAX].FSize
    JAE     TBits.Error
    MOV     EAX,[EAX].FBits
    BT      [EAX],Index
    SBB     EAX,EAX
    AND     EAX,1
end;
{$ENDIF X86ASM}

我开始将32位的ASM代码转换为64位。经过搜索,我发现需要将EAX引用更改为64位编译器的RAX。我最终得到了以下第一行:

CMP     Index,[RAX].FSize

这段代码编译通过,但在运行时会出现访问冲突。我尝试了几种组合(例如MOV ECX,[RAX].FSize),但在尝试访问[RAX].FSize时仍然出现访问冲突。当我查看由Delphi编译器生成的汇编代码时,它看起来我的[RAX].FSize应该是正确的。

Unit72.pas.143: MOV     ECX,[RAX].FSize
00000000006963C0 8B8868060000     mov ecx,[rax+$00000668]

同时,这是 Delphi 生成的代码:

Unit72.pas.131: if (Index >= FSize) or (Index < 0) then
00000000006963CF 488B4550         mov rax,[rbp+$50]
00000000006963D3 8B4D58           mov ecx,[rbp+$58]
00000000006963D6 3B8868060000     cmp ecx,[rax+$00000668]
00000000006963DC 7D06             jnl TForm72.GetBit + $24
00000000006963DE 837D5800         cmp dword ptr [rbp+$58],$00
00000000006963E2 7D09             jnl TForm72.GetBit + $2D

在这两种情况下,生成的汇编程序都使用[rax+$00000668]来访问FSize。在Delphi 64位汇编中访问类字段的正确方法是什么?
这可能听起来像一件奇怪的优化事情,但是64位Pascal版本的汇编器似乎不是很高效。我们会多次调用这个例程,根据各种因素,它需要执行的时间长达5倍。

你手头有 Pascal 版本吗? - David Heffernan
@DavidHeffernan 将其放到问题中。不想在问题中放太多 Delphi VCL 源代码。64 位版本不同。 - Graymatter
这是VCL代码吗?如果是的话,我可以找到它。 - David Heffernan
尽管$668是一个比我预期的偏移量要高得多的值,但至少编译器在使用该值时是一致的。然而,你计算的RAX与编译器的不同。编译器表示“Self”指针存储在RBP+$50处;它并不假设该值已经在RAX中。同样,对于RBP+$58处的“Index”参数也是如此。这涉及到x86和x86_64之间调用约定的差异。 - Rob Kennedy
@RobKennedy 那么我只需要在汇编的第一行添加 MOV RAX, Self 就可以保持代码不变了吗? - Graymatter
1个回答

4
基本问题在于您正在使用错误的寄存器。在x64调用约定中,Self作为隐式参数传递,在所有其他参数之前。这意味着它是通过RCX而不是RAX传递的。
因此,Self通过RCX传递,而Index通过RDX传递。坦率地说,我认为在内联汇编中使用参数名称是一个错误,因为它们隐藏了该参数是通过寄存器传递的事实。如果您碰巧覆盖了RDX,那么就会改变Index的表面值。
因此,if语句可能被编码为:
CMP     EDX,[RCX].FSize
JNL     TBits.Error
CMP     EDX,0
JL      TBits.Error

就我所知,这是一个非常简单的函数实现,我认为您不需要使用任何堆栈空间。在x64中,您有足够的寄存器可以完全使用易失性寄存器来完成此操作。


你正在查看未经优化的代码。启用优化器后,请查看生成的汇编代码。顺便说一下,我认为代码并不那么糟糕,但它执行了两次除法,而只需要执行一次。我会考虑使用Pascal编写它,但是使用DivMod函数。我的函数如下: - David Heffernan
1
procedure DivMod(Dividend, Divisor: Cardinal; out Quotient, Remainder: Cardinal); {$IFDEF CPUX86} asm PUSH EBX MOV EBX,EDX XOR EDX,EDX DIV EBX MOV [ECX],EAX MOV EBX,Remainder MOV [EBX],EDX POP EBX end; {$ELSE IF Defined(CPUX64)} asm .NOFRAME MOV EAX,ECX MOV ECX,EDX XOR EDX,EDX DIV ECX MOV [R8],EAX MOV [R9],EDX end; {$ELSE} {$Message Error 'Unrecognised platform.'} {$ENDIF} - David Heffernan
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Graymatter
一定会听从那个建议。我已经有一段时间没有使用汇编语言(CHASM)了,所以很多东西都变了。 - Graymatter
特别强调,如果这些参数是通过寄存器传递的话,使用参数名往往会出现问题,因为这会隐藏修改寄存器后,通过名称访问参数是不安全的事实。请改用寄存器名称。 - Rudy Velthuis
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接