Reputation: 1239
I have a problem. I have following x86 delphi code which is written in ASM. I need to port this to AMD64?
type
TCPUID = array[1..4] of Longint;
function GetCID : TCPUID; assembler; register;
asm
push ebx
push edi
mov edi, eax
mov eax, 1
dw $A20F
stosd
mov eax, ebx
stosd
mov eax, ecx
stosd
mov eax, edx
stosd
pop edi
pop ebx
end;
I have never programmed in assembly, does anyone know what the port would be or how I would go about changing it.
Upvotes: 6
Views: 3096
Reputation: 613013
Here's my version, for both x86 and x64:
function GetCPUID: TCPUID;
asm
{$IF Defined(CPUX86)}
push ebx
push edi
mov edi, eax
mov eax, 1
xor ecx,ecx
cpuid
mov [edi+$0], eax
mov [edi+$4], ebx
mov [edi+$8], ecx
mov [edi+$c], edx
pop edi
pop ebx
{$ELSEIF Defined(CPUX64)}
mov r8, rbx
mov r9, rcx
mov eax, 1
cpuid
mov [r9+$0], eax
mov [r9+$4], ebx
mov [r9+$8], ecx
mov [r9+$c], edx
mov rbx, r8
{$IFEND}
end;
One of the nice things about x64 is that there are a lot more registers available, many of which are volatile. So we can make use of that scratch space and avoid touching main memory at all. Well obviously we have to touch main memory to return the result.
Since RBX is nonvolatile we preserve its value. All the other registers that we modify are volatile and so we need not preserve them. I can't think of any way to simplify this further.
This can readily be extended to allow the input to CPUID
to be passed as an argument:
function GetCPUID(ID: Integer): TCPUID;
asm
{$IF Defined(CPUX86)}
push ebx
push edi
mov edi, edx
xor ecx,ecx
cpuid
mov [edi+$0], eax
mov [edi+$4], ebx
mov [edi+$8], ecx
mov [edi+$c], edx
pop edi
pop ebx
{$ELSEIF Defined(CPUX64)}
mov r8, rbx
mov r9, rcx
mov eax, edx
cpuid
mov [r9+$0], eax
mov [r9+$4], ebx
mov [r9+$8], ecx
mov [r9+$c], edx
mov rbx, r8
{$ELSE}
{$Message Fatal 'GetCPUID has not been implemented for this architecture.'}
{$IFEND}
end;
This assumes a sub-leaf value of 0, passed in ECX. Again, if you wish to pass that, it is easy enough:
function GetCPUID(Leaf, Subleaf: Integer): TCPUID;
asm
{$IF Defined(CPUX86)}
push ebx
push edi
mov edi, ecx
mov ecx, edx
cpuid
mov [edi+$0], eax
mov [edi+$4], ebx
mov [edi+$8], ecx
mov [edi+$c], edx
pop edi
pop ebx
{$ELSEIF Defined(CPUX64)}
mov r9,rcx
mov ecx,r8d
mov r8,rbx
mov eax,edx
cpuid
mov [r9+$0], eax
mov [r9+$4], ebx
mov [r9+$8], ecx
mov [r9+$c], edx
mov rbx, r8
{$ELSE}
{$Message Fatal 'GetCPUID has not been implemented for this architecture.'}
{$IFEND}
end;
Upvotes: 6
Reputation: 27493
I am not Win64 assembler guru, but the next translation worked for me (tested on 64-bit free pascal):
program project1;
{$mode delphi}
{$asmmode intel}
type
TCPUID = array[1..4] of Longint;
function GetCID: TCPUID;
asm
push rbx
push rdi
mov rdi, rcx
mov eax, 1
cpuid
mov [rdi],eax
add rdi,4
mov [rdi],ebx
add rdi,4
mov [rdi],ecx
add rdi,4
mov [rdi],edx
pop rdi
pop rbx
end;
var ID: TCPUID;
begin
ID:= GetCID;
Writeln(ID[1], '-', ID[2], '-', ID[3], '-', ID[4]);
Readln;
end.
Upvotes: 10
Reputation: 16045
I never worked with CPUID so i don't know for sure what it does. But from common sense and Wikipedia (if those sources would suffice) my advices are:
Try to
1) remove "assembler;" keyword - obsolete
1.1) optionally remove "register;" - it is by default and has little value for no-parameters function. Also Wikipedia tells that has no effect in Win64.
2) if possible - rephrase this as procedure GetCID (out data: TCPUID);
.
If need function - i'd rather made inline
wrapper in Pascal - just to keep definition simple and obvious. That is a good advice to author - to keep not-automated things simplistic and leave syntax-sugar automation to Pascal, especially when you have no experience and any not simple trick can confuse you and cause you to type broken code. KISS principle.
3) remove push ebx/pop ebx
3.1) i think push edi/popedi to be removed as well. But to be on safe side - i'd changed them to push rdi
and pop rdi
The article does not tell that some registers should be saved or preserved:
http://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions
Nor that requirement is told at http://docwiki.embarcadero.com/RADStudio/XE3/en/Assembly_Procedures_and_Functions
4) mov edi, eax
-> mov rdi, rcx
4.1) you may add cld
command next after it, as an extra safety measure. But it should be overcautious and redundant.
The rest should be the same as it seems x64 has the same convention for CPUID as x86 mode - no mentio nof x64 mode at http://en.wikipedia.org/wiki/CPUID#EAX.3D1:_Processor_Info_and_Feature_Bits
To sum it up it should be like
type
TCPUID = packed array[1..4] of INT32;
function GetCID : TCPUID; inline;
begin
GetCID_Implementation(Result);
end;
procedure GetCID_Implementation (out buffer: TCPUID);
asm
mov rdi, rcx // mov edi, eax
// RCX/EAX is Delphi/Windows pointer to 1st parameter
// RDI/EDI is CPU pointer to output buffer
// cld - optionally, should not be needed
// state of cld should be preserved by all other functions
xor eax, eax // shorter way for eax := 1
inc eax
dw $A20F // call CPUID(eax==1),
// output (according to wikipedia) is in eax,edx,ecx,ebx 32-bit registers
stosd // *( (INT32*)RDI )++ := eax
mov eax, ebx // xchg eax, ebx would be shorter,on that
stosd
mov eax, ecx // but Delphi XE2 is broken with xchg eax
stosd
mov eax, edx
stosd
end;
Upvotes: -1