This file can be included to 64bit Windows programs written in EuroAssembler.
The library contains macroinstructions which extend generic (pseudo)instruction
CALL, PROC, ENDPROC
.
Macroinstructions Invoke, Procedure and EndProcedure hide the prologue and epilogue of
Microsoft x64 calling convention [CallingConv]
, 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.
Macroinstructions in this library communicate with each other
at assembly time using the preprocessing %variables
%LocalFrameSize
and %NrOfArg_ProcedureName
.
fastcall HEAD
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. Nevertheless, the number of arguments internally supplied by invokation is at least four, and they create shadow space on the stack. When the Procedure is declared with 0, 1, 2 or 3 parameters, the missing arguments are surrogated with the current contents of RCX, RDX, R8 or R9, respectively.
Macro Invoke takes care of stack alignment to OWORD just before execution of instruction
CALL MyFunction
(step 7. in the following example). RSP at the Invoke entry may 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 beginning. Instructions of step 2. (PUSH RSP
and ADD [RSP],8
) are emitted always but they will be skipped at run-time
CALL
) is executed,
and the stack is restored to equilibrium (OrigRSP) after Invoke, no matter if it was OWORD aligned or not.
Macro Invoke will push all parameters on stack in reversed order, and only then it loads registers for parameter transfer (RCX,RDX,R8,R9) from the stack. Thanks to this, invokation parameters may be provided in arbitraty order by any registers, including those scratch ones.
Macro Procedure copies first four parameters 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 is redundant when the Procedure is invoked by the macro Invoke from this library, but it may be useful if our Procedure is invoked from 3rd party code, for instance as a callback from MS Windows.
Such implementation of FastCall convention is not much economical but it is compatible and allows to Invoke 64bit functions from 3rd party libraries or from Windows ABI, and it allows to create FastCall functions invokable from other libraries or as system callback procedures.
If your procedure is called from your own programs only, I recommend to prefer more efficient private register calling convention, tailored to the function's needs.
Following diagram shows the stack frame created by invoking MyFunction procedure defined above.
Prologue of MyFunction Procedure Arg1, Arg2, Arg3
will assign "global" %variables
%NrOfArg_MyFunction %SETA 4
and %LocalFrameSize %SETA 8+16
, which will be used later by epilogue in EndProcedure MyFunction
.
Step 1 is executed always.
Step 2 is executed only if OrigRSP is OWORD-aligned (otherwise it's jumped over).
Steps 3..6 create shadow space.
Steps 1..7 are emitted by macro Invoke.
Step 8 is emitted by macro Procedure.
Steps 9..10 are emitted by macro LocalVar.
Steps 11 are statements of the procedure body supplied by the programmer.
Steps 12..14 are emitted by macro EndProcedure.
Steps 15..16 are emitted by macro Invoke.
; Assignment of generic formal names of Procedure's arguments: %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 declares FastCall-procedure prologue which, unlike ordinary pseudoinstruction
PROC, accepts ordinal operands.
Using of the macro Procedure requires the corresponding EndProcedure
be used in the same program.
Macro operands will be assigned to preprocessing %variables using the operand formal name prefixed with a percent sign.
Move Procedure
in the previous example will assign
%NrOfArg_Move %SET 3 ; This %variable propagates to the corresponding macro EndProcedure Move
.
%LocalFrameSize %SETA 0 ; This %variable propagates to macro LocalVar, if used in Move Procedure
.
%Source %SET RBP+16 ; This %variable represens formal name of Arg1.
%Destination %SET RBP+24 ; This %variable represens formal name of Arg2.
%Size %SET RBP+32 ; This %variable represens formal name of Arg3.
Procedure %MACRO Operands
LblCheck %IF "%:" == ""
%ERROR ID=5921, 'Macro "Procedure" requires a label.'
%EXITMACRO Procedure
%ENDIF LblCheck
%%NrOfArg_%: %SETX %# ; Name of this %variable must be unique (for the case when Procedures are nested).
%LocalFrameSize %SETA 0 ; This %variable maintains the size of local stack-memory variables.
ArgNr %FOR 1..%#, STEP=+1
%%%*{%ArgNr} %SETX RBP+(8+%ArgNr<<3) ; Assign the formal name to a %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.
%IF %# >= 1 && '%1' !== 'RCX' ; Make sure that first four arguments are accessible
MOV [RBP+16],RCX ; both in registers and on stack as %Arg1..%Arg4.
%ENDIF
%IF %# >= 2 && '%2' !== 'RDX'
MOV [RBP+24],RDX
%ENDIF
%IF %# >= 3 && '%3' !== 'R8'
MOV [RBP+32],R8
%ENDIF
%IF %# >= 4 && '%4' !== 'R9'
MOV [RBP+40],R9
%ENDIF
%ENDMACRO Procedure
SUB RSP,%LocalVarSize
to reserve room on the machine stack.
%LocalFrameSize
which was initialized in macro Procedure.RBP-8
.
Block LocalVar Size=1024 ; %Block is now assigned with RBP-1032
.
ClearLocalVar ; Fill %Block and %BlockSize with 0.
MOV [%BlockSize],1K, DATA=QWORD
LEA RAX,[%Block]
; etc...
EndProcedure ProcNameLocalVar %MACRO Size=8 LblCheck %IF "%:" == "" %ERROR ID=5922, 'Macro "LocalVar" requires a label.' %EXITMACRO LocalVar %ENDIF LblCheck OrdCheck %IF %# %ERROR ID=5923, 'Macro "LocalVar" does not expect ordinal parameters.' %ENDIF OrdCheck %: %COMMENT ; This empty block makes the label of macro invokation void, %ENDCOMMENT %: ; so it does not declare a symbol. %LocalVarSize %SETA (%Size + 7) & ~7 ; Round up to he nearest multiple of QWORD size. %LocalFrameSize %SETA %LocalFrameSize + %LocalVarSize SUB RSP, %LocalVarSize %%%: %SETX RBP-%LocalFrameSize ; Assign formal %name to the id %: specified as LocalVar label. %ENDMACRO LocalVar
We could as well decide to initialize each local variable individually, in this case the macro ClearLocalVar will not be used in the Procedure body at all.
RSP
, cleared size is specified with "global" variable
%LocalFrameSize
.ClearLocalVar %MACRO %IF %LocalFrameSize ; Do nothing if no LocalVar was used in this Procedure. PUSH RDI MOV RDI,RSP ADD RDI,8 MOV ECX,%LocalFrameSize/8 XOR EAX,EAX REP STOSQ POP RDI %ENDIF %ENDMACRO ClearLocalVar
Macro EndProcedure terminates context of the previously opened
Procedure
. This epilogue of FastCall convention will discard local variables defined with
LocalVar using machine instructions
MOV RSP,RBP
, restore caller's frame pointer with POP RBP
and then return with near RET
to the parent code
which the Procedure was Invoked from. Operand 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.
EndProcedure %MACRO ProcName OpCheck %IF "%ProcName" === "" %ERROR ID=5924, 'Macro "EndProcedure" requires one operand.' %EXITMACRO EndProcedure %ENDIF OpCheck %ProcNameStrip %SET %ProcName %WHILE "%ProcNameStrip[%&]" === ":" ; Get rid of trailing colons, if used. %ProcNameStrip %SET %ProcNameStrip[1..%&-1] %ENDWHILE %NrOfArg %SET2 %%NrOfArg_%ProcNameStrip NestChck %IF "%NrOfArg" === "" %ERROR ID=5925, '"%ProcName Procedure" statement missing.' %EXITMACRO EndProcedure %ENDIF NestChck MOV RSP,RBP POP RBP RET 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 on stack as QWORDs,
starting from the last. The first four parameters are then loaded to RCX, RDX, R8, R9,
even when the Procedure requires less than four arguments.
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.
Invoke %MACRO ProcName, Arg1, Arg2,,,, %IF %# = 1 ; If no arguments. %ArgList %SET RCX,RDX,R8,R9 %ENDIF %IF %# = 2 ; If one argument. %ArgList %SET %2,RDX,R8,R9 %ENDIF %IF %# = 3 ; if two arguments. %ArgList %SET %2,%3,R8,R9 %ENDIF %IF %# = 4 ; If three arguments. %ArgList %SET %2,%3,%4,R9 %ENDIF %IF %# >= 5 ; If four or more arguments. %ArgList %SET %*{2..} %ENDIF %ArgNr %SETL %ArgList ; Number of arguments, at least 4. PUSH RSP ; Save original stack pointer value. TEST SPL,1000b ; Test stack OWORD alignment at run-time. %IF %ArgNr & 1b ; When the number of arguments is odd, JNZ .Invoke%.: ; skip padding if RSP is unaligned. %ELSE ; When the number of arguments is even, JZ .Invoke%.: ; skip padding if RSP is OWORD aligned. %ENDIF PUSH RSP ; Additional PUSH for padding the stack ADDQ [RSP],8 ; Update copy of original stack pointer value. .Invoke%.: ; A temporary local label, individualized with expansion number %. %WHILE %ArgNr > 0 PUSHQ %ArgList{%ArgNr} ; Push all arguments on stack, begin with the last one. %ArgNr %SETA %ArgNr-1 %ENDWHILE %IF "%ArgList{1}" !== "RCX" MOV RCX,[RSP+0] ; Load first four arguments to registers due to FastCall convention. %ENDIF %IF "%ArgList{2}" !== "RDX" MOV RDX,[RSP+8] %ENDIF %IF "%ArgList{3}" !== "R8" MOV R8,[RSP+16] %ENDIF %IF "%ArgList{4}" !== "R9" MOV R9,[RSP+24] %ENDIF CALL %ProcName %ArgNr %SETL %ArgList ; Reload the number of arguments again. LEA RSP,[RSP+8*%ArgNr] ; Let RSP skip all pushed arguments POP RSP ; and restore its original value (possibly not OWORD aligned). %ENDMACRO Invoke
ENDHEAD fastcall