Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing Delphi Class Fields in 64 bit inline assembler

I am trying to convert the Delphi TBits.GetBit to inline assembler for the 64 bit version. The VCL source looks like this:

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}

I started converting the 32 bit ASM code to 64 bit. After some searching, I found out that I need to change the EAX references to RAX for the 64 bit compiler. I ended up with this for the first line:

CMP     Index,[RAX].FSize

This compiles but gives an access violation when it runs. I tried a few combinations (e.g. MOV ECX,[RAX].FSize) and get the same access violation when trying to access [RAX].FSize. When I look at the assembler that is generated by the Delphi compiler, it looks like my [RAX].FSize should be correct.

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

And the Delphi generated code:

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

In both cases, the resulting assembler uses [rax+$00000668] for FSize. What is the correct way to access a class field in Delphi 64bit Assembler?

This may sound like a strange thing to optimize but the assembler for the 64bit pascal version doesn't appear to be very efficient. We call this routine a large number of times and it takes anything up to 5 times as long to execute depending on various factors.

like image 307
Graymatter Avatar asked Dec 13 '25 16:12

Graymatter


1 Answers

The basic problem is that you are using the wrong register. Self is passed as an implicit parameter, before all others. In the x64 calling convention, that means it is passed in RCX and not RAX.

So Self is passed in RCX and Index is passed in RDX. Frankly, I think it's a mistake to use parameter names in inline assembler because they hide the fact that the parameter was passed in a register. If you happen to overwrite either RDX, then that changes the apparent value of Index.

So the if statement might be coded as

CMP     EDX,[RCX].FSize
JNL     TBits.Error
CMP     EDX,0
JL      TBits.Error

FWIW, this is a really simple function to implement and I don't believe that you will need to use any stack space. You have enough registers in x64 to be able to do this entirely using volatile registers.

like image 162
David Heffernan Avatar answered Dec 15 '25 13:12

David Heffernan