Test of X64 Calling Convention

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
Post Reply
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Test of X64 Calling Convention

Post by MichaelW »

This is just a quick and dirty test I did to fill in some details that the documentation I have been referencing left out.

Code: Select all

#include "crt.bi"

''----------------------------------------------------------------------------
'' For the Microsoft X64 calling convention the first four integer or pointer
'' arguments, taken in left to right order as they appear in the parameter
'' list, are passed in RCX, RDX, R8, and R9. Any additional arguments, taken
'' in right to left order as they appear in the parameter list, are passed on
'' the stack immediately above the 32-byte "shadow" space allocated by the
'' caller.
''----------------------------------------------------------------------------

dim shared as integer _rcx, _rdx, _r8, _r9, rsp40, rsp48, _rsp
dim shared as integer _rcx_address, _rcx_rip_relative
dim shared as integer L0, L1, L2
dim shared as zstring*23 format = !"%s\n%I64d\n%I64d\n%I64d\n\n"
dim shared as zstring*11 freebasic = "FreeBASIC"

function Test naked ( arg1 as integer, _
                      arg2 as integer, _
                      arg3 as integer, _
                      arg4 as integer, _
                      arg5 as integer, _
                      arg6 as integer ) as integer
    asm
    
      '' This to compare the encoded size for the two addressing forms:      
      0:
        mov   _rcx, rcx
        mov   _rdx, rdx
        mov   _r8, r8
        mov   _r9, r9
        mov   rax, [rsp+40]
        mov   rsp40, rax
        mov   rax, [rsp+48]
        mov   rsp48, rax
        mov   _rsp, rsp
      1:  
        mov   _rcx[rip], rcx
        mov   _rdx[rip], rdx
        mov   _r8[rip], r8
        mov   _r9[rip], r9
        mov   rax, [rsp+40]
        mov   rsp40[rip], rax
        mov   rax, [rsp+48]
        mov   rsp48[rip], rax
        mov   _rsp[rip], rsp        
      2: 

        lea   rax, _rcx
        mov   _rcx_address, rax        
        lea   rax, _rcx[rip]
        mov   _rcx_rip_relative, rax
      
        lea   rcx, format
        lea   rdx, freebasic
        mov   r8, 123
        mov   r9, 456
        push  789               '' push 5th printf arg onto stack
        sub   rsp, 32           '' allocate 32-byte shadow space
        call  printf
        add   rsp, 40           '' release shadow space and "pop" 5th arg
        
        mov   QWORD PTR L0, OFFSET 0b
        mov   QWORD PTR L1, OFFSET 1b
        mov   QWORD PTR L2, OFFSET 2b
      
        mov   rax, 9876543210   '' set function return value
        
        ret                     '' this will fault if RSP adjustments not right
        
    end asm    
end function

''--------------------------------------------------------------------
'' Returns the largest alignment, or zero if passed a null pointer.
''--------------------------------------------------------------------

function alignment naked( byval address as any ptr ) as integer
  asm
      xor   rax, rax   '' prep for return zero       
      bsf   rcx, rcx   '' scan RCX for LS set bit, return index in RCX
      jz    0f         '' return if no set bit
      mov   rax, 1     '' set bit 0
      shl   rax, cl    '' shift left by index
  0:
      ret
  end asm
end function

print Test( 1, 2, 3, 4, 5, 6 )
print
print _rcx
print _rdx
print _r8
print _r9
print rsp40
print rsp48
print
print hex(_rcx_address,16)
print hex(_rcx_rip_relative,16)
print
print alignment(_rsp)
print
print L1-L0
print L2-L1
sleep

Code: Select all

FreeBASIC
123
456
789

 9876543210

 1
 2
 3
 4
 5
 6

000000000040C030
000000000040C030

 8

 66
 59
RIP-relative addressing obviously works, and the encoding is somewhat smaller, but it's still not clear to me how it works. If I use LEA to get the address of a shared variable operand, and to get the address of an RIP-relative operand of the variable, I get the same address for both, so it looks like either the address is RIP-relative in any case (unlikely), or the RIP-relative operand does not work like a normal indirect memory operand.
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Re: Test of X64 Calling Convention

Post by MichaelW »

This corrects an error in the above code:

Code: Select all

#include "crt.bi"

''----------------------------------------------------------------------------
'' For the Microsoft X64 calling convention the first four integer or pointer
'' arguments, taken in left to right order as they appear in the parameter
'' list, are passed in RCX, RDX, R8, and R9. Any additional arguments, taken
'' in right to left order as they appear in the parameter list, are passed on
'' the stack immediately above the 32-byte "shadow" space allocated by the
'' caller.
''----------------------------------------------------------------------------

dim shared as integer _rcx, _rdx, _r8, _r9, rsp40, rsp48, _rsp
dim shared as integer _rcx_address, _rcx_rip_relative
dim shared as integer L0, L1, L2
dim shared as zstring*23 format = !"%s\n%I64d\n%I64d\n%I64d\n\n"
dim shared as zstring*11 freebasic = "FreeBASIC"

function Test naked ( arg1 as integer, _
                      arg2 as integer, _
                      arg3 as integer, _
                      arg4 as integer, _
                      arg5 as integer, _
                      arg6 as integer ) as integer
    asm
    
      '' This to compare the encoded size for the two addressing forms:      
      0:
        mov   _rcx, rcx
        mov   _rdx, rdx
        mov   _r8, r8
        mov   _r9, r9
        mov   rax, [rsp+40]
        mov   rsp40, rax
        mov   rax, [rsp+48]
        mov   rsp48, rax
        mov   _rsp, rsp
      1:  
        mov   _rcx[rip], rcx
        mov   _rdx[rip], rdx
        mov   _r8[rip], r8
        mov   _r9[rip], r9
        mov   rax, [rsp+40]
        mov   rsp40[rip], rax
        mov   rax, [rsp+48]
        mov   rsp48[rip], rax
        mov   _rsp[rip], rsp        
      2: 

        lea   rax, _rcx
        mov   _rcx_address, rax        
        lea   rax, _rcx[rip]
        mov   _rcx_rip_relative, rax
        
        '' Stack must be kept 16-byte aligned, but at this point, because
        '' of the 8-byte return address the stack is misaligned.
        '' To correct the alignment the stack frame size should be 16n+8,
        '' so allow room for the 32-byte shadow space and one argument on
        '' the stack, which will restore the 16-byte alignment.
        
        sub   rsp, 40
        
        '' Set up the arguments and call printf.
        
        lea   rcx, format
        lea   rdx, freebasic
        mov   r8, 123
        mov   r9, 456
        mov   QWORD PTR [rsp+32], 789   '' 5th printf arg
        call  printf
        add   rsp, 40                   '' release shadow space and "pop" 5th arg
        
        mov   QWORD PTR L0, OFFSET 0b
        mov   QWORD PTR L1, OFFSET 1b
        mov   QWORD PTR L2, OFFSET 2b
      
        mov   rax, 9876543210   '' set function return value
        
        ret                     '' this will fault if RSP adjustments not right
        
    end asm    
end function

''--------------------------------------------------------------------
'' Returns the largest alignment, or zero if passed a null pointer.
''--------------------------------------------------------------------

function alignment naked( byval address as any ptr ) as integer
  asm
      xor   rax, rax   '' prep for return zero       
      bsf   rcx, rcx   '' scan RCX for LS set bit, return index in RCX
      jz    0f         '' return if no set bit
      mov   rax, 1     '' set bit 0
      shl   rax, cl    '' shift left by index
  0:
      ret
  end asm
end function

print Test( 1, 2, 3, 4, 5, 6 )
print
print _rcx
print _rdx
print _r8
print _r9
print rsp40
print rsp48
print
print hex(_rcx_address,16)
print hex(_rcx_rip_relative,16)
print
print alignment(_rsp)
print
print L1-L0
print L2-L1
sleep

Code: Select all

FreeBASIC
123
456
789

 9876543210

 1
 2
 3
 4
 5
 6

000000000040C030
000000000040C030

 8

 66
 59
More information on RIP-relative addressing is here, and a good short article, that I had forgotten about, here.
Post Reply