;<![CDATA[

IFDEF PFF32
 PVOID TYPEDEF DWORD
ELSE
 PVOID TYPEDEF QWORD
ENDIF

IFDEF PFF32

; init: no temp register is being used

PFF_EBX = 0
PFF_ECX = 0

; 64-bit general registers (which may hold only a pointer) are simply
; EQUated to 32-bit ones for 32-bit mode

rax TEXTEQU <eax>
;...

; usage of any of new general registers causes calling of pff_r macro,
; which move the emulated value into free reserved register (eBX or eCX)

r8  EQU <pff_r (r8, d)>
r9  EQU <pff_r (r9, d)>
;...

r9d  EQU <pff_r (r9, d)>
;...

r11w EQU <pff_r (r11, w)>
r12w EQU <pff_r (r12, w)>
;...

; set registers mapping to reserved registers so it is possible to test
; whether a register is emulated or not

rax_mapping TEXTEQU <>
;...

r8_mapping  TEXTEQU <ebx>
r9_mapping  TEXTEQU <ebx>
;...

r9d_mapping  TEXTEQU <ebx>
;...

r11w_mapping TEXTEQU <bx>
r12w_mapping TEXTEQU <bx>
;...

; Macro pff_get_tmp_r
;
; This macro returns appropriate reserved register, which would be currently
; used with given emulated register
;
; If no reserved register is available, macro returns blank string.
;
; Input:
;  regex   emulated register name with "_" postfix

pff_get_tmp_r MACRO regex:REQ
LOCAL postfix

 IF PFF_EBX AND PFF_ECX
   EXITM <>				; no reserved register available
 ENDIF

 postfix SUBSTR <regex>, @SizeStr (regex) - 1

%IFIDN <postfix>, <d_>		; dword register
   IFE PFF_EBX
     EXITM <ebx>
   ELSE
     EXITM <ecx>
   ENDIF
%ELSEIFIDN <postfix>, <w_>	; word
   IFE PFF_EBX
     EXITM <bx>
   ELSE
     EXITM <cx>
   ENDIF
%ELSEIFIDN <postfix>, <b_>	; byte
   IFE PFF_EBX
     EXITM <bl>
   ELSE
     EXITM <cl>
   ENDIF
 ELSE				; qword
   IFE PFF_EBX
     EXITM <ebx>
   ELSE
     EXITM <ecx>
   ENDIF
 ENDIF
ENDM

; Macro pff_r
;
; This macro moves the value of an emulated register to free reserved
; register and returns the register.
;
; Input:
;  regex   emulated register name
;  size    emulated register size

pff_r MACRO regex:REQ, size:REQ
 IFIDNI <size>, <b>
   IFE PFF_EBX
     PFF_EBX = 1
     mov bl, pff.global&regex&size
     EXITM <bl>
   ELSE
     PFF_ECX = 1
     mov cl, pff.global&regex&size
     EXITM <cl>
   ENDIF

 ELSEIFIDNI <size>, <w>
   IFE PFF_EBX
     PFF_EBX = 1
     mov bx, pff.global&regex&size
     EXITM <bx>
   ELSE
     PFF_ECX = 1
     mov cx, pff.global&regex&size
     EXITM <cx>
   ENDIF

 ELSE
   IFE PFF_EBX
     PFF_EBX = 1
     mov ebx, pff.global&regex&size
     EXITM <ebx>
   ELSE
     PFF_ECX = 1
     mov ecx, pff.global&regex&size
     EXITM <ecx>
   ENDIF
 ENDIF
ENDM

; Macro pff_meta
;
; This macro provides the facility for two-operand instructions.
;
; Input:
;  type   type of operation: read/write
;  op     the operation itself (mov, add, cmp, test, ...)
;  op1    destination operand
;  op2    source operand

pff_meta MACRO type:REQ, op:REQ, op1:REQ, op2:REQ
LOCAL src, dst
LOCAL tmp

 ; add "_" to prevent expansion of possible emulated register

 IFE @InStr (, op1&_, <[>)	; destination operand is not a memory location

   ; if the destination is an emulated register...

   tmp TEXTEQU op1&_mapping

  %IFNB <tmp>

     ; if the source is a memory location, load it first to tmp register

     IF @InStr (, op2&_, <[>)	; source operand is a memory location
       mov op1&_mapping, op2

       PFF_ECX = 0		; now, the second reserved register can be used

       ; don't load current emulated value if the operation is MOV

       IFDIF <op>, <mov>
         dst TEXTEQU op1
       ELSE
         dst TEXTEQU pff_get_tmp_r (op1&_)
       ENDIF

       ; don't perform MOV operation since it is unnecessary in this case
       ; (MOV is here actually performed by the the former and the latter MOV)

       IFDIF <op>, <mov>
         op dst, op1&_mapping
       ENDIF

       IFIDN <type>, <write>	; if mov, add, etc., write it back
         mov [pff.global&op1&], dst
       ENDIF

     ELSE			; source operand is not a memory location
       IFDIF <op>, <mov>
         dst TEXTEQU op1
       ELSE
         dst TEXTEQU pff_get_tmp_r (op1&_)
       ENDIF
       op dst, op2
       IFIDN <type>, <write>
         mov [pff.global&op1&], dst
       ENDIF
     ENDIF

   ELSE
     op op1, op2
   ENDIF

 ELSE	; op1 is a memory location

   IFNDEF op2&_mapping		; catch immediate source operand
     tmp TEXTEQU <>
   ELSE
     tmp TEXTEQU op2&_mapping
   ENDIF

  %IFNB <tmp>			; source is an emulated register

     ; if the destination is a memory location, load first its address
     ; to tmp register

     IF @InStr (, op1&_, <[>)	; destination operand is a memory location
       lea ebx, op1

       PFF_ECX = 0		; now, the second reserved register can be used

       src TEXTEQU op2		; load current emulated value

       op [ebx], src

     ELSE			; destination operand is not a memory location
       op op1, op2&_mapping
     ENDIF

   ELSE
     op op1, op2
   ENDIF
 ENDIF

 ; set both temp registers as unused

 PFF_EBX = 0
 PFF_ECX = 0
ENDM

; Macros supplying original instructions

mov@ MACRO op1:REQ, op2:REQ
 pff_meta write, mov, op1, op2
ENDM

add@ MACRO op1:REQ, op2:REQ
 pff_meta write, add, op1, op2
ENDM

cmp@ MACRO op1:REQ, op2:REQ
 pff_meta read, cmp, op1, op2
ENDM

; Internal macro pff_global_r
;
; This internal macro is just used to declare global memory space for
; emulated registers; see PFF struct

pff_global_r MACRO regex:REQ
 UNION
  global&regex     DWORD ?
  global&regex&d   DWORD ?
  global&regex&w   WORD ?
  global&regex&b   BYTE ?
 ENDS
ENDM

PFF STRUCT
 pff_global_r r8
 pff_global_r r9
 pff_global_r r10
 pff_global_r r11
 pff_global_r r12
 pff_global_r r13
 pff_global_r r14
 pff_global_r r15
PFF ENDS

ELSE ; IF PFF32

mov@ TEXTEQU <mov>
add@ TEXTEQU <add>
cmp@ TEXTEQU <cmp>

ENDIF

.DATA?
IFDEF PFF32
pff PFF <>   ; reserve space for emulated registers
ENDIF

;]]>

