This file can be included to 64bit Windows programs written in EuroAssembler.
The library contains macroinstructions which extend generic (pseudo)instructions
CALL, PROC, ENDPROC
.
Macroinstructions Invoke, Procedure and EndProcedure implement the prologue and epilogue of
Microsoft x64 calling convention [MSx64conv]
, where the arguments are pushed backwards and they are removed by the caller.
The first 4 arguments are provided in registers RCX, RDX, R8, R9 (or in XMM0, XMM1, XMM2, XMM3
when they are floating-point numbers).
The block of code defined between macros Procedure .. EndProcedure can be called by macro Invoke or as a callback procedure from Windows function, for instance see WndProc in sample projects.
Macro Invoke can be also used to call functions from third-party static or dynamically linked libraries. Nevertheless, for invocation of Windows functions it's better to use specialized macros WinABI or WinAPI.
Implementation of FastCall convention in €ASM allows to use formal %names for accessing Procedure parameters and local stack-memory variables.
Number of arguments provided in macro Invoke must exactly match the number of arguments declared in macro Procedure.
Macro Invoke takes care of stack alignment to OWORD just before execution of instruction
CALL MyProc
(step 5. in the following example). RSP at the Invoke entry might already have been OWORD aligned,
or it may be only QWORD aligned (=unaligned).
That is why RSP will be pushed once or twice in the prologue. Instructions of step 2. (PUSH RSP
and ADD [RSP],8
) are emitted always but they will be skipped at run-time
Thanks to this, RSP is always OWORD aligned before step 5. (CALL MyProc
) is executed,
and the stack is restored to equilibrium (OrigRSP) after Invoke, no matter if it was OWORD aligned or not.
If macro SaveToShadow is used in Procedure block, it copies first four parameters from registers into shadow space. Thanks to this, those parameters are available not only in RCX,RDX,R8,R9, but as stack variables %Arg1,%Arg2,%Arg3,%Arg4 alias their formal operand %names, too.
This implementation is compatible with [MSx64conv] convention and it allows to Invoke 64bit external or imported functions available in 3rd party libraries or in Windows ABI. It also allows to create FastCall functions invokable from other libraries or as system callback procedures.
I'm not sure if all crash analysers and debuggers will be able to unwind exception raised in Procedure, though. If your procedure is called from your own programs only, I recommend to abandon bad-designed Microsoft FastCall convention and use more efficient private register calling convention, tailored to the function's needs.
Following diagram shows the stack frame created by invoking MyProc defined in the example above.
Macros of fastcall convention will create and update following "global" %variables at asm-time:%`NrOfArguments`MyProc %SETA 5
%`UsesRegList`MyProc %SET RDI
%`LocalVarSize`MyProc %SETA 8+16
fastcall HEAD
%Arg8 %SET RBP+72 %Arg7 %SET RBP+64 %Arg6 %SET RBP+56 %Arg5 %SET RBP+48 %Arg4 %SET RBP+40 %Arg3 %SET RBP+32 %Arg2 %SET RBP+24 %Arg1 %SET RBP+16
This macro Procedure declares beginning of FastCall-procedure, which unlike ordinary pseudoinstruction
PROC, expects ordinal operands having been pushed on stack or loaded to registers.
Using of the macro Procedure requires the corresponding EndProcedure
be used in the same program.
Macro operands formal names will be assigned to preprocessing %variables
represented with the formal name prefixed by percent sign. Arguments are
available in Procedure block as formal names declared here in Procedure prototype (%OperandX)
or as generic names (%ArgX).
Pay attention that this is true for 5th and higher argument only. The first four arguments
are passed in registers, so if we want to access them by formal or generic name,
they have to be saved to shadow space in the beginning of our procedure, either by
SaveToShadow, or manually with
Move Procedure
in the previous example will assign
%`NrOfArguments`Move %SET 3 ; This %variable propagates to the corresponding macro EndProcedure Move
.
%`UsesRegList`Move %SET ; This %variable propagates to the macro Uses and EndProcedure.
%`LocalVarSize`Move %SETA 0 ; This %variable propagates to the macro LocalVar and EndProcedure.
%Source %SET RBP+16 ; This %variable represents formal name of Arg1.
%Destination %SET RBP+24 ; This %variable represents formal name of Arg2.
%Size %SET RBP+32 ; This %variable represents formal name of Arg3.
Macro Uses
in the previous example will assign
%Uses_Move %SET RSI,RDI ; This %variable propagates to the corresponding macro EndProcedure Move
.
Invoke Move, RSI, OutBuffer, SIZE# OutBuffer ; Example of procedure invocation.
Procedure %MACRO FormalName1, FormalName2,,,,
%IF "%:" === ""
%ERROR ID=5921, 'Macro "Procedure" requires a label.'
%EXITMACRO Procedure
%ENDIF
%%`NrOfArg`%: %SETX %# ; Initialize with number of arguments.
%%`UsesRegList`%: %SETX ; Initialize as empty list.
%%`LocalVarSize`%: %SETX 0 ; Initialize as zero.
ArgNr %FOR 1..%#, STEP=+1
%%%*{%ArgNr} %SETX RBP+8*(%ArgNr+1) ; Assign the formal name to the corresponding %variable.
%ENDFOR ArgNr
%::: PROC %=*, NESTINGCHECK=OFF ; Open the namespace and define entry symbol from macro label %:
as GLOBAL.
PUSH RBP
MOV RBP,RSP ; Initialize the frame pointer.
%ENDMACRO Procedure
Macro SaveToShadow stores the first four arguments of fast-called Procedure in 64bit mode to the shadow space reserved by Invoke. This enables the first four arguments be referred by their formal names or by generic names %Arg1, %Arg2, %Arg3, %Arg4.
Macro SaveToShadow should be used inside Procedure..EndProcedure block, near its beginning.
When it is omitted, the first four arguments are available only in registers RCX, RDX, R8, R9 (or XMM0..XMM3)
and the shadow space contains undefined garbage.
When some of arguments contains floating-point number, it is passed to Procedure in SIMD register instead of GPR, and it must be therefore copied to the corresponding GPR prior to SaveToShadow. Example (Radius is FP):
SaveToShadow %MACRO %IF "%^PROC" === "" %ERROR ID=5926,'Macro "%0" is unexpected here.' %ENDIF MOV [%Arg1],RCX MOV [%Arg2],RDX MOV [%Arg3],R8 MOV [%Arg4],R9 %ENDMACRO SaveToShadow
Macro Uses specifies which callee-save registers does the Procedure use, so they are pushed on stack here (and they will be restored in EndProcedure epilogue).
Macro Uses can be used in 64bit mode only, right after the statement Procedure and before local stack variables are defined with LocalVar.
Callee-save registers RBX,RSI,RDI,R12..R15,XMM6..XMM15, should be enumerated here
only if they are actually used in Procedure..EndProcedure block.
Callee-save registers RBP,RSP should not be mentioned here, they are always saved automatically in Procedure prologue.
It is useless to enumerate scratch registers RCX,RDX,R8,R9,XMM1..XMM5 here,
because the caller of our Procedure cannot expect them to be preserved.
Registers RAX,XMM0 may not be enumerated here, because they wouldn't return the expected value after their restoration.
%`UsesRegList`ProcedureName
in reversed order. This %variable will be used by EndProcedure for restoration of callee-save registers.Uses %MACRO Register1,Register2,... %IF "%^PROC" === "" %ERROR ID=5926,'Macro "%0" is unexpected here.' %ENDIF %UsesRegList %SET2 %%`UsesRegList`%^PROC reg %FOR %* %IF REGTYPE#(%reg) = 'Q' ; General-purpose 64bit register. PUSHQ %reg %UsesRegList %SET %reg,%UsesRegList %ENDIF %IF REGTYPE#(%reg) = 'X' ; SIMD XMM register. SUB RSP,8 MOVQ [RSP],%reg %UsesRegList %SET %reg,%UsesRegList %ENDIF %IF REGTYPE#(%reg) != 'Q' && REGTYPE#(%reg) != 'X' %ERROR ID=5927,'Macro "Uses" does not support operand "%reg".' %ENDIF %ENDFOR reg %%`UsesRegList`%^PROC %SETX %UsesRegList %ENDMACRO Uses
SUB RSP,%LocalVarSize
to reserve room on the machine stack.
%`LocalVarSize`ProcedureName
which was initialized in macro Procedure
and which will be used for zeroing local variables in
ClearLocalVar and for discarding local variables in
EndProcedure.RBP-8
.
BlockSize LocalVar ; %BlockSize
is now assigned with RBP-16
(8+8).
Block LocalVar Size=1024 ; %Block
is now assigned with RBP-1040
(8+8+1024).
ClearLocalVar ; Fill %Block and %BlockSize with 0.
MOV [%BlockSize],1K, DATA=QWORD
LEA RDI,[%Block]
; more instructions...
EndProcedure ProcNameLocalVar %MACRO Size=8 %IF "%^PROC" === "" %ERROR ID=5926,'Macro "%0" is unexpected here.' %EXITMACRO LocalVar %ENDIF %IF "%:" === "" %ERROR ID=5922, 'Macro "%0" requires a label.' %EXITMACRO LocalVar %ENDIF %IF %# %ERROR ID=5923, 'Macro "%0" does not expect ordinal parameters.' %ENDIF %: %COMMENT ; This empty comment block makes the label of macro void, %ENDCOMMENT %: ; so it does not declare a symbol. %ThisSize %SETA (%Size + 7) & ~7 ; Round up to the nearest multiple of 8. %LocalVarSize %SET2 %%`LocalVarSize`%^PROC ; Total size of previously defined local variables. %LocalVarSize %SETA %LocalVarSize + %ThisSize ; Add this variable size. SUB RSP, %Size ; Stack memory allocation. %%`LocalVarSize`%^PROC %SETX %LocalVarSize ; Propagate the new sum to the following LocalVar definitions and to EndProcedure. %UsesRegList %SET2 %%`UsesRegList`%^PROC %UsesRegListLength %SETL %UsesRegList ; Number of registers pushed by macro Uses. %UsesFrameSize %SETA %UsesRegListLength * 8 %%%: %SETX RBP-%UsesFrameSize-%LocalVarSize ; Assign formal %name to the id %: specified as LocalVar label. %ENDMACRO LocalVar
We could as well decide to initialize each local variable individually, e.g.
MOVQ [%MyLocalVar],0
in this case the macro ClearLocalVar will not be used in the Procedure body at all.
RSP
,
its size is specified with "global" variable
%`LocalVarSize`ProcedureName
.ClearLocalVar %MACRO %IF "%^PROC" === "" %ERROR ID=5926,'Macro "%0" is unexpected here.' %EXITMACRO ClearLocalVar %ENDIF %LocalVarSize %SET2 %%`LocalVarSize`%^PROC %IF %LocalVarSize ; Do nothing if no LocalVar was used in this Procedure (%LocalVarSize is 0). PUSH RCX,RDI LEA RDI,[RSP+2*8] ; Skip pushed RCX,RDI. MOV ECX,%LocalVarSize >> 3 XOR EAX,EAX REP STOSQ POP RDI,RCX %ENDIF %ENDMACRO ClearLocalVar
Macro EndProcedure terminates context of the previously opened Procedure . This epilogue of FastCall convention will
MOV RSP,RBP
,POP registers
,POP RBP
and thenRET
to the parent code
which the Procedure was invoked from.Operands are not removed from stack, that's the job of Invoke.
Programmer should never use explicit machine instructionRET
to return from the block defined withProcedure .. EndProcedure
.
If premature return is required, jump to the label ofEndProcedure
statement instead.
Procedure
statement.Invoke
statement.EndProcedure %MACRO ProcName %IF %# <> 1 %ERROR ID=5924, 'Macro "EndProcedure" requires one operand.' %EXITMACRO EndProcedure %ENDIF %ProcNameStripped %SET %ProcName %WHILE '%ProcNameStripped[%&]' === ':' ; Get rid of trailing colon(s), if used. %ProcNameStripped %SET %ProcNameStripped[1..%&-1] %ENDWHILE %IF '%ProcNameStripped' !=== '%^PROC' %ERROR ID=5925, 'Nesting mismatch, "%ProcName Procedure" missing.' %EXITMACRO EndProcedure %ENDIF %LocalVarSize %SET2 %%`LocalVarSize`%^PROC %IF %LocalVarSize ADD RSP,%LocalVarSize ; Discard local variables. %ENDIF %UsesRegList %SET2 %%`UsesRegList`%^PROC reg %FOR %UsesRegList ; Callee-save registers saved by macro Uses. %IF REGTYPE#(%reg) = 'Q' ; General-purpose 64bit register. POPQ %reg %ENDIF %IF REGTYPE#(%reg) = 'X' ; SIMD XMM register. MOVQ %reg,[RSP] ADD RSP,8 %ENDIF %ENDFOR reg POP RBP ; Restore caller's frame pointer. RET ; Return below Invoke which called %ProcName. ENDP %ProcName, NESTINGCHECK=OFF ; Terminate the namespace. %ENDMACRO EndProcedure
Macro Invoke is a replacement of standard CALL instruction which can pass parameters
to the Procedure in FastCall convention. Arguments are pushed backwards on stack as QWORDs,
starting from the last to the fifth. The first four parameters are loaded to RCX, RDX, R8, R9
and not pushed on stack. Nevertheless, room for the first four arguments is always reserved on stack
(so called shadow space) even when the invoked function has less than four arguments.
When the Procedure expects argument in floating-point format instead of integer, pointer or immediate,
the first four arguments are loaded to XMM0, XMM1, XMM2, XMM3 rather than to GPR.
Stack will be OWORD aligned before the CALL instruction is performed.
Invoked procedure does not remove arguments from stack, it terminates with a simple RETN. Epilogue of macro Invoke restores RSP to its original value.
It is necessary to Invoke a procedure with exactly the same number of arguments which were declared by the Procedure macro.
Argument of Invoke may have many formats:
XMM15
,RBX
,FS
or GS
,0
or -11
,GMEM_FIXED
,="Hello, world!"
or MyCallback:
.[RBP-40]
or [MyTable+RSI]
or [=Q 16G]
,Each argument may be suffixed with type specificator #SS
or #SD
(case insensitive),
which signalizes that it represents floating-point value in Scalar Single or
Scalar Double precision format, and that it should be therefore passed to the ProcName in XMM register instead of GPR.
Suffix is not necessary in 5th and higher arguments (they are passed via machine stack regardless of their type).
Suffix is not necessary with XMM register (argument passed in XMM is always assumed to contain floating-point number).
From parsing reason do not use single apostrophe ' in string literals used as macro arguments.
Please prefer double quote:
Invoke MyFunction,="Text"
instead of Invoke MyFunction, ='Text'
FastCall Invoke in MS Windows does not keep the original contents of
Rflags, RAX, RCX, RDX, R8..R11, XMM0..XMM5
.
It expects and keeps Direction Flag on zero.
Registers RAX, RCX, RDX, R8, R9, XMM0..XMM3 should not be used
as macro arguments, because they are being overwritten in prologue. Or they must be used
in the exact ABI-specified order, e.g. Invoke MyProcedure, RCX, XMM1, R8, XMM3
.
Invoke %MACRO ProcName, Arg1, Arg2,,,, PUSH RSP ; Store original stack pointer value (equilibrum). TEST SPL,1000b ; Test stack OWORD alignment at run-time. %IF %# & 1b || %# <= 5 ;>If the number of Procedure arguments is 0,1,2,3,4,6,8,10,, JZ .Invoke%.: ; store 2nd copy of equilibrum when RSP is OWORD-unaligned. %ELSE ; If the number of arguments is 5,7,9,11,,, JNZ .Invoke%.: ; store 2nd copy of equilibrum when RSP is OWORD-aligned. %ENDIF PUSH RSP ; Store and update 2nd copy of original RSP (equilibrum). ADDQ [RSP],8 ; Those two instructions aren't executed if RSP was properly aligned. .Invoke%.: %GPR %SET RCX,RDX,R8,R9 ; Declare order of registers used for first four arguments. %SIMD %SET XMM0,XMM1,XMM2,XMM3 %ArgNr %SETA %# ; Number of arguments + 1. %WHILE %ArgNr > 1 ; Pass all arguments, start with the last one. %Arg %SET %*{%ArgNr} %ArgNr %SETA %ArgNr - 1 ; %ArgNr is now the ordinal argument number ..5,4,3,2,1. %IF '%Arg[%&-2..%&-1]'=='#S' ; If suffix #SS or #SD is present. %suffix %SET %Arg[%&-1..%&]; %suffix is now SS or SD. %Arg %SET %Arg[1..%&-3] ; Remove the suffix from %Arg. %ELSE %suffix %SET Q ; Otherwise %suffix is Q (use MOVQ instead of MOVSS or MOVSD). %ENDIF a5 %IF %ArgNr > 4 ; Argument %5 and higher is passed by stack. s5 %IF TYPE#(SEGMENT#(%Arg))='N' ; Nonrelocable (scalar) argument. x5 %IF REGTYPE#(%Arg)='X' SUB RSP,8 ; Pseudo"push" XMM on stack. MOV%suffix [RSP],%Arg %ELSE x5 PUSHQ %Arg ; Other non-XMM scalar argument PUSH as is. %ENDIF x5 %ELSE s5 ; Argument needs relocation. m5 %IF '%Arg[1]' === '[' ; Argument is m64, e.g. [RelocSymbol+RSI]. PUSHQ %Arg %ELSE M5 ; Otherwise it is relocable offset (pointer). LEA RAX,[%Arg],ADDR=ABS PUSH RAX ; Borrow RAX for pushing pointers. %ENDIF m5 %ENDIF s5 %ELSE a5 ; Argument %1..%4 will be loaded to GPR or SIMD register. s4 %IF TYPE#(SEGMENT#(%Arg))='N' ; Nonrelocable (scalar) argument or register. q4 %IF REGTYPE#(%Arg) = 'Q' ; Argument is GPR. i4 %IF '%suffix'==='Q' ; Argument is integer, it goes to GPR. %IF '%GPR{%ArgNr}' !== '%Arg' ; Emit only if it's not already there. MOVQ %GPR{%ArgNr},%Arg %ENDIF %ELSE i4 ; Argument is float, it goes to SIMD. MOVQ %SIMD{%ArgNr},%Arg %ENDIF i4 %ELSE q4 ; Argument is not GPR. x4 %IF REGTYPE#(%Arg)='X' ; Argument is SIMD. %IF '%SIMD{%ArgNr}' !== '%Arg' ; Emit only if it's not already there. MOV%suffix %SIMD{%ArgNr},%Arg %ENDIF %ELSE x4 ; Nonrelocable argument is not SIMD neither GPR. m4 %IF 'Arg[1]' === '[' ; Argument is m64, e.g. [RBP+8] or [RBP+8]#SS. i3 %IF '%suffix' === 'Q' ; Argument is integer, it goes to GPR. MOVQ %GPR{%ArgNr},%Arg %ELSE i3 MOV%suffix %SIMD{%ArgNr},%Arg ; Argument is float, it goes to SIMD. %ENDIF i3 %ELSE m4 ; Argument is immediate scalar. %IF '%suffix' !=== 'Q' %ERROR ID=5958,"Floating-point immediate argument is not supported." %ENDIF MOVQ %GPR{%ArgNr},%Arg %ENDIF m4 %ENDIF x4 %ENDIF q4 %ELSE s4 ; Argument needs relocation. m3 %IF '%Arg[1]' === '[' ; Argument is m64, e.g. [RelocSymbol+RSI]. i2 %IF '%suffix' === 'Q' ; Argument is integer, it goes to GPR. MOVQ %GPR{%ArgNr},%Arg %ELSE i2 MOV%suffix %SIMD{%ArgNr},%Arg %ENDIF i2 %ELSE m3 ; Argument is immediate pointer. i1 %IF '%suffix' === 'Q' ; Argument is integer, it goes to GPR. LEA %GPR{%ArgNr},[%Arg],ADDR=ABS %ELSE i1 %ERROR ID=5958,"Floating-point immediate argument is not supported." LEA RAX,[%Arg],ADDR=ABS MOVQ %SIMD{%ArgNr},RAX %ENDIF i1 %ENDIF m3 %ENDIF s4 %ENDIF a5 %ENDWHILE SUB RSP,4*8 ; Make room for shadow space. RSP is now OWORD-aligned. CALL %ProcName %IF %# > 5 ; When returned from the invoked procedure, remove arguments from stack. %ArgNr %SETA %# - 1 %ELSE %ArgNr %SETA 4 %ENDIF LEA RSP,[RSP+8*%ArgNr] ; Let RSP skip all pushed arguments, keeping Rflags, POP RSP ; and restore RSP to equilibrum from 1st or 2nd copy. %ENDMACRO Invoke
ENDHEAD fastcall