;General Options:
;================

;show some general hints?
define	show_hints	true

;show how much debugging? Not implemented yet
;define	debug_level	2



;Options for the push and pop macros:
;====================================

;if set to "true", use native "push" and "pop" instructions when possible.
;if set to "false", code all "push" and "pop" instructions with "mov"s,
;which will increase code size, but might run faster on some CPUs.
define	native_pushpop	true

;what sse mov instruction variant to use for non-native "push"/"pop"s.
;reasonable values are
;for SSE1+:	movups
;for SSE2+:	movups, movupd, movdqu
;
;the movaXX instructions would be possible, but this requires some
;stack aligning work, which is quiet difficult and inefficient on a per
;register basis
define	sse_mov	movups

;don't modify the flags when working on the stack?
;this is only relevant for non-native "push"/"pop"s,
;as native ones always preserve the flags (except the popf).
;should be set to true, unless you know what you are doing and to save some
;code size.
;Note that yo can still save the flags with "pushf" at some point and restore
;them when needed with "popf", but the flags will be destroyed inside
;the "pushf" "popf" block from the beginning!
define	preserve_stack_flags	true




;Options to the proc, save, return, endp and call macros:
;========================================================

;If the code size of the restoring part is above this value, then all "return"s
;will simply perform jmps to the "endp" epilog.
;Else each "return" will have it's own register restoring part.
;Useful for doing performance/size tradeoffs.
define	max_code_size_inline_return	1

;use mmx/sse instructions to optimize non-native prolog and epilog even when no
;mmx/sse registers have been specified. This is currently not implemented,
;but this may become useful in the future for optimizing savings/restorings
;of multiple large memory operands.
;mmx/sse instructions are nevertheless always used when explicitly saving
;mm? or xmm? registers
;define	implicit_mmx	off
;define	implicit_sse	off;off sse1 sse2 sse3

\t	fix 0x9
\n	fix 0xA

macro	displn	[txt] {
common
  match all, txt \{
forward
    display	txt
common
  \}
  display	\n
}

virtual	at	0
	lldt	[eax]
  load	.prefix0	from 0
end virtual
if .prefix0 = 0x67
  displn	'error: program not compiled with use32!'
end if

;TEST START
;this macro checks the code generated by code_macro against the specified chksum
macro	chk_code	code_macro, chksum {
  virtual at 0
    code_macro
	align	8
	db	0
;do some simple hashing over the generated code

;This hash function has been designed in 2 hours of drunkeness, so you
;shouldn't rely on it for critical purposes
;It's design goals were:
;easy to implement in fasm
;acceptable speed
;not have the problems that simple checksums that "add"s or "xor"s all data have
local	acc, aux, tmp
    load	acc qword	from 0
    aux= acc

    repeat ($ shr 3) - 1
      load	dat qword	from % shl 3
      acc= (acc shl 43 + acc shr 21) xor dat
      tmp= acc
      acc= acc shl 53 + acc shr 11 + dat + 0x4000000000000000
      acc= (acc shl 25 + acc shr 39) xor aux
      aux= tmp
    end repeat
  end virtual
  display	'=> Checksum of ',`code_macro,': '
  ;;;TODO: optionally append hex code of checksum with a hex display macro
  if acc = chksum
    displn	'ok',\n
  else
    displn	'failed',\n
  end if
}
;END TEST

macro	_add_esp	acc {
;;;TODO: merge the stack pointer increments of multiple register pushs/pops
;into 1 increment to save size and speed (if the registers are in 1 line only)
;I know how to do this, just haven't got enough time yet. Will be fixed ASAP
  if preserve_stack_flags eq true
	lea	esp,[esp+acc]
  else
    if acc < 0
	sub	esp,-acc
    else
	add	esp,acc
    end if
  end if
}

macro	_pu	arg {
local	op, size
;  display	'DEBUG(3): pushing: '					;DEBUG3
  size	equ
  op	equ arg
  last_push_size= 4
  match first rest, arg \{
;    display	\`first\#' '						;DEBUG3
    size	equ first
    op		equ rest
    last_push_size= -1
  \}
;  match	all, op \{						;DEBUG3
;  irps	i, all \\{							;DEBUG3
;    display	\\`i							;DEBUG3
;  \\}									;DEBUG3
;  \}									;DEBUG3
;  displn								;DEBUG3

  match	=dqword, size \{ last_push_size= 16 \}
  match	=qword , size \{ last_push_size= 8 \}
  match	=dword , size \{ last_push_size= 4 \}
  match	=word  , size \{ last_push_size= 2 \}				;WORD
  if op in <ax,cx,dx,bx,sp,bp,si,di>;,cs,ss,ds,es,fs,gs			;WORD
    last_push_size= 2							;WORD
  end if								;WORD

  if last_push_size = 2						;HINT & WORD
    match	=true, show_hints \{				;HINT & WORD
      displn 'hint: pushing words may cause stack-alignment'#\	;HINT & WORD
	     ' errors on some 32bit systems.'			;HINT & WORD
    \}								;HINT & WORD
  end if							;HINT & WORD

  if op eq flags
;    displn	'DEBUG(4): is flags'					;DEBUG4
    if size eq | size eq dword
	pushf
    else if size eq word						;WORD
	pushfw								;WORD
    else;all other sizes aren't supported
	pushd	arg
    end if

    rept	8	i:0 \{
  else if op eq mm\#i
;    displn	'DEBUG(4): is MMX-register'				;DEBUG4
    last_push_size= 8
	_add_esp	-last_push_size
	movq	size [esp],op
  else if op eq xmm\#i
;    displn	'DEBUG(4): is SSE-register'				;DEBUG4
    last_push_size= 16
	_add_esp	-last_push_size
	sse_mov	size [esp],op
    \}
  else if op eqtype 0 | op eqtype "" | op eqtype 1.0
;    displn	'DEBUG(4): is immediate'				;DEBUG4
    if last_push_size = -1 | size eq dqword
	pushd	arg
    end if
    if (size eq & op >= -0x80000000 & op <= 0xFFFFFFFF) | arg eqtype 1.0 \
    | size eq dword
      if native_pushpop eq true
	push	arg
      else
	_add_esp	-last_push_size
	mov	dword [esp],op
      end if
    else if size eq word						;WORD
      if native_pushpop eq true						;WORD
	pushw	op							;WORD
      else								;WORD
	_add_esp	-last_push_size					;WORD
	mov	word [esp],op						;WORD
      end if								;WORD
    else;if size eq qword
      last_push_size= 8
local	tmp
      tmp= arg
      if native_pushpop eq true
	push	tmp shr 32
	push	tmp and 0xFFFFFFFF
      else
	_add_esp	-last_push_size
	mov	dword [esp],tmp and 0xFFFFFFFF
	mov	dword [esp+4],tmp shr 32
      end if
    end if
  else if op eqtype [ebx]
;    displn	'DEBUG(4): is memory'					;DEBUG4
    match	addr], op \{
      if size eq dqword
	push	dword addr+12]
	push	dword addr+8]
	push	dword addr+4]
	push	dword addr]
      else if size eq qword
	push	dword addr+4]
	push	dword addr]
      else if size eq word						;WORD
	pushw	arg							;WORD
      else
	push	arg
      end if
    \}
  else if native_pushpop eq false;if op eqtype eax
;    displn	'DEBUG(4): is general pupose register'			;DEBUG4
	_add_esp	-last_push_size
	mov	[esp],arg
  else
;    displn	'DEBUG(4): is general pupose register'			;DEBUG4
	push	arg
  end if
}

macro	_po	arg {
local	op, size
;  display	'DEBUG(3): pushing: '					;DEBUG3
  size	equ
  op	equ arg
  last_pop_size= 4
  match first rest, arg \{
;    display	\`first\#' '						;DEBUG3
    size	equ first
    op		equ rest
    last_pop_size= -1
  \}
;  match	all, op \{						;DEBUG3
;  irps	i, all \\{							;DEBUG3
;    display	\\`i							;DEBUG3
;  \\}									;DEBUG3
;  \}									;DEBUG3
;  displn								;DEBUG3

  match	=dqword, size \{ last_pop_size= 16 \}
  match	=qword , size \{ last_pop_size= 8 \}
  match	=dword , size \{ last_pop_size= 4 \}
  match	=word  , size \{ last_pop_size= 2 \}				;WORD
  if op in <ax,cx,dx,bx,sp,bp,si,di>;,cs,ss,ds,es,fs,gs			;WORD
    last_pop_size= 2							;WORD
  end if								;WORD

  if last_pop_size = 2						;HINT & WORD
    match	=true, show_hints \{				;HINT & WORD
      displn 'hint: popping words may cause stack-alignment'#\	;HINT & WORD
	     ' errors on some 32bit systems.'			;HINT & WORD
    \}								;HINT & WORD
  end if							;HINT & WORD

  if op eq flags
;    displn	'DEBUG(4): is flags'					;DEBUG4
    if size eq | size eq dword
	popf
    else if size eq word						;WORD
	popfw								;WORD
    else;all other sizes aren't supported
	popd	arg
    end if

    rept	8	i:0 \{
  else if op eq mm\#i
;    displn	'DEBUG(4): is MMX-register'				;DEBUG4
    last_pop_size= 8
	_add_esp	last_pop_size
	movq	op,size [esp]
  else if op eq xmm\#i
;    displn	'DEBUG(4): is SSE-register'				;DEBUG4
    last_pop_size= 16
	_add_esp	last_pop_size
	sse_mov	op,size [esp]
    \}
  else if op eqtype [ebx]
;    displn	'DEBUG(4): is memory'					;DEBUG4
    match	addr], op \{
      if size eq dqword
	pop	dword addr]
	pop	dword addr+4]
	pop	dword addr+8]
	pop	dword addr+12]
      else if size eq qword
	pop	dword addr]
	pop	dword addr+4]
      else if size eq word						;WORD
	popw	arg							;WORD
      else
	pop	arg
      end if
    \}
  else if native_pushpop eq false;if op eqtype eax
;    displn	'DEBUG(4): is general purpose register'			;DEBUG4
	_add_esp	last_pop_size
	mov	arg,[esp]
  else
;    displn	'DEBUG(4): is general purpose register'			;DEBUG4
	pop	arg
  end if
}

;TEST START
macro	_chk_pupo {
  macro	_do_pupo \{
  \local	dat
dat	dq	0xFEDCBA9876543210
	_pu	flags
	_pu	word flags
	_pu	dword flags
	_pu	0x8B
	_pu	0xE38B
	_pu	0xAD10E38B
	_pu	0x000000470000008B
	_pu	0x0000F3470000E38B
	_pu	0x72C1F347AD10E38B
	_pu	word 0x8B
	_pu	word 0xE38B
	_pu	dword 0x8B
	_pu	dword 0xAD10E38B
	_pu	qword 0x8B
	_pu	qword 0xAD10E38B
	_pu	qword 0x000000470000008B
	_pu	qword 0x72C1F347AD10E38B
	_pu	"f"
	_pu	"fl"
	_pu	"flat"
	_pu	"flat ass"
	_pu	word "f"
	_pu	word "fl"
	_pu	dword "f"
	_pu	dword "flat"
	_pu	qword "f"
	_pu	qword "flat ass"
	_pu	4.669
	_pu	dword 4.669
	_pu	qword 4.669
	_pu	word [esp+4]
	_pu	word [esi+ecx*8+200h]
	_pu	word [dat]
	_pu	dword [esp+4]
	_pu	dword [esi+ecx*8+200h]
	_pu	dword [dat]
	_pu	qword [esp+4]
	_pu	qword [esi+ecx*8+200h]
	_pu	qword [dat]
	_pu	dqword [esp+4]
	_pu	dqword [esi+ecx*8+200h]
	_pu	dqword [dat]
	_pu	ax
	_pu	ds
	_pu	ebx
	_pu	mm3
	_pu	xmm5

	_po	flags
	_po	word flags
	_po	dword flags
	_po	word [esp+4]
	_po	word [esi+ecx*8+200h]
	_po	word [dat]
	_po	dword [esp+4]
	_po	dword [esi+ecx*8+200h]
	_po	dword [dat]
	_po	qword [esp+4]
	_po	qword [esi+ecx*8+200h]
	_po	qword [dat]
	_po	dqword [esp+4]
	_po	dqword [esi+ecx*8+200h]
	_po	dqword [dat]
	_po	ax
	_po	ds
	_po	ebx
	_po	mm3
	_po	xmm5

	;The following instructions are intended to NOT work. Negatives test
	;ERROR	START
	;_pu	byte flags
	;_pu	fword flags
	;_pu	tword flags
	;_pu	byte 0x8B
	;_pu	word 0xAD10E38B
	;_pu	dword 0x72C1F347AD10E38B
	;_pu	fword 0xF347AD10E38B
	;_pu	tword 0x72C1F347AD10E38B
	;_pu	"flat assembler"
	;_pu	byte "f"
	;_pu	byte "fl"
	;_pu	word "flat"
	;_pu	dword "flat ass"
	;_pu	fword "flat a"
	;_pu	qword "flat assembler"
	;_pu	tword "flat assem"
	;_pu	dqword "flat assembler"
	;_pu	byte 4.669
	;_pu	word 4.669
	;_pu	fword 4.669
	;_pu	tword 4.669
	;_pu	byte [esp+4]
	;_pu	byte [esi+ecx*8+200h]
	;_pu	byte [dat]
	;_pu	fword [esp+4]
	;_pu	fword [esi+ecx*8+200h]
	;_pu	fword [dat]
	;_pu	tword [esp+4]
	;_pu	tword [esi+ecx*8+200h]
	;_pu	tword [dat]
	;_pu	al
	;_pu	byte al
	;_pu	word al
	;_pu	dword al
	;_pu	fword al
	;_pu	qword al
	;_pu	tword al
	;_pu	dqword al
	;_pu	byte ax
	;_pu	word ax
	;_pu	dword ax
	;_pu	fword ax
	;_pu	qword ax
	;_pu	tword ax
	;_pu	dqword ax
	;_pu	byte ds
	;_pu	word ds
	;_pu	dword ds
	;_pu	fword ds
	;_pu	qword ds
	;_pu	tword ds
	;_pu	dqword ds
	;_pu	byte ebx
	;_pu	word ebx
	;_pu	dword ebx
	;_pu	fword ebx
	;_pu	qword ebx
	;_pu	tword ebx
	;_pu	dqword ebx
	;_pu	byte mm3
	;_pu	word mm3
	;_pu	dword mm3
	;_pu	fword mm3
	;_pu	qword mm3
	;_pu	tword mm3
	;_pu	dqword mm3
	;_pu	byte xmm5
	;_pu	word xmm5
	;_pu	dword xmm5
	;_pu	fword xmm5
	;_pu	qword xmm5
	;_pu	tword xmm5
	;_pu	dqword xmm5

	;_po	byte flags
	;_po	fword flags
	;_po	tword flags
	;_po	0x8B
	;_po	0xE38B
	;_po	0xAD10E38B
	;_po	0x72C1F347AD10E38B
	;_po	byte 0x8B
	;_po	word 0x8B
	;_po	word 0xE38B
	;_po	word 0x72C1F347AD10E38B
	;_po	dword 0x8B
	;_po	dword 0xAD10E38B
	;_po	dword 0x72C1F347AD10E38B
	;_po	fword 0xF347AD10E38B
	;_po	qword 0x8B
	;_po	qword 0x72C1F347AD10E38B
	;_po	tword 0x72C1F347AD10E38B
	;_po	"f"
	;_po	"fl"
	;_po	"flat"
	;_po	"flat ass"
	;_po	"flat assembler"
	;_po	byte "f"
	;_po	word "f"
	;_po	word "fl"
	;_po	word "flat"
	;_po	dword "f"
	;_po	dword "flat"
	;_po	dword "flat ass"
	;_po	fword "flat a"
	;_po	qword "f"
	;_po	qword "flat ass"
	;_po	qword "flat assembler"
	;_po	tword "flat assem"
	;_po	dqword "flat assembler"
	;_po	4.669
	;_po	byte 4.669
	;_po	word 4.669
	;_po	dword 4.669
	;_po	fword 4.669
	;_po	tword 4.669
	;_po	qword 4.669
	;_po	byte [esp+4]
	;_po	byte [esi+ecx*8+200h]
	;_po	byte [dat]
	;_po	fword [esp+4]
	;_po	fword [esi+ecx*8+200h]
	;_po	fword [dat]
	;_po	tword [esp+4]
	;_po	tword [esi+ecx*8+200h]
	;_po	tword [dat]
	;_po	al
	;_po	byte al
	;_po	word al
	;_po	dword al
	;_po	fword al
	;_po	qword al
	;_po	tword al
	;_po	dqword al
	;_po	byte ax
	;_po	word ax
	;_po	dword ax
	;_po	fword ax
	;_po	qword ax
	;_po	tword ax
	;_po	dqword ax
	;_po	byte ds
	;_po	word ds
	;_po	dword ds
	;_po	fword ds
	;_po	qword ds
	;_po	tword ds
	;_po	dqword ds
	;_po	byte ebx
	;_po	word ebx
	;_po	dword ebx
	;_po	fword ebx
	;_po	qword ebx
	;_po	tword ebx
	;_po	dqword ebx
	;_po	byte mm3
	;_po	word mm3
	;_po	dword mm3
	;_po	fword mm3
	;_po	qword mm3
	;_po	tword mm3
	;_po	dqword mm3
	;_po	byte xmm5
	;_po	word xmm5
	;_po	dword xmm5
	;_po	fword xmm5
	;_po	qword xmm5
	;_po	tword xmm5
	;_po	dqword xmm5
	;END	ERROR
  \}
  define	show_hints		false
  define	sse_mov			movups

  define	native_pushpop		true
  define	preserve_stack_flags	false
  _do_pupo
  define	preserve_stack_flags	true
  _do_pupo
  define	native_pushpop		false
  _do_pupo

  restore	native_pushpop
  restore	preserve_stack_flags
  restore	preserve_stack_flags
  restore	native_pushpop
  restore	sse_mov
  restore	show_hints
  purge	_do_pupo
}

;uncomment the following line to test the "_pu" and "_po" macros
chk_code	_chk_pupo,0x54352A8478CDEBF7
;END TEST

macro	_parse_args	sngl_m,mult_m, [arg] {
common
;here goes da big preprocessor parser, the match of your life!
local	not_symb, size_prefix, cat_stat, cat
  ;off	flank_on	on	flank_off	flank_tmp
  define	cat_stat	off
  irps	i, arg \{
    ;displn 'DEBUG(3): parsing argument: '\#\`i				;DEBUG3
    match	=true, size_prefix \\{
      ;displn 'DEBUG(4): there was a size prefix'			;DEBUG4
      define	not_symb	false
      match	[, i \\\{ define not_symb true \\\}
      match	=false, not_symb \\\{
	cat	equ cat i
	define	cat_stat	flank_off
      \\\}
    \\}

    define	size_prefix	false
    match	=off, cat_stat \\{
      match	[	, i \\\{ define	cat_stat flank_on \\\}
      match	=dword  , i \\\{ define	cat_stat flank_tmp \\\}
      match	=qword  , i \\\{ define	cat_stat flank_tmp \\\}
      match	=dqword , i \\\{ define	cat_stat flank_tmp \\\}
      ;not recommended though
      match	=byte	, i \\\{ define	cat_stat flank_tmp \\\}
      match	=word	, i \\\{ define	cat_stat flank_tmp \\\}
      match	=fword	, i \\\{ define	cat_stat flank_tmp \\\}
      match	=tword	, i \\\{ define	cat_stat flank_tmp \\\}
    \\}
    match	=flank_tmp, cat_stat \\{
      ;displn 'DEBUG(4): this is a size prefix'			;DEBUG4
      define	size_prefix	true
      define	cat_stat	flank_on
    \\}

    define	not_symb	false
    match	=,, i \\{ define not_symb true \\}
    match	=false, not_symb \\{
      match	=on, cat_stat \\\{
	match	all, cat \\\\{ cat equ all\\\\#i \\\\}
      \\\}
      match	=flank_on, cat_stat \\\{
	cat	equ i
	define	cat_stat	on
      \\\}
      match	], i \\\{ define cat_stat flank_off \\\}

      match	=off, cat_stat \\\{ sngl_m i \\\}
      match	=flank_off, cat_stat \\\{
	mult_m	cat
	define	cat_stat	off
      \\\}
    \\}
  \}
}

;offset of first argument within current stack frame at the beginning of proc
define	_proc_args_ofs	4
;size of 1 pushed argument on stack
define	_proc_arg_size	4

;name of current procedure
;setup by "proc" macro, should be treated readonly by actual code
.proc_name	equ

.._proc_prolog= -0x100000000
define	.._proc_prolog	undefined				;rst_syms_endp2

macro	proc	name,[arg] {
common
  displn	'DEBUG: entering: '#`name				;DEBUG
name#:
  .proc_name	equ name

local	args_size						;rst_syms_endp2
  .args		equ						;rst_syms_endp
  _args_size	equ 0
  .args_ofs= _proc_args_ofs

  .saved_regs	equ
  .saved_regs_size= 0

  match	all, arg \{
forward
    displn	'DEBUG(2): defining argument: .'\#\`arg			;DEBUG2

    ;args arg, the angry pirat!
    .args	equ .\#arg .args				;rst_syms_endp
    .\#arg	equ esp+.args_ofs + _args_size
    _args_size	equ _proc_arg_size + _args_size
common
  \}
  .args_size= _args_size

  restore	.._proc_prolog					;rst_syms_endp2
  .._proc_prolog= $
  define	.._proc_prolog	undefined			;rst_syms_endp2

  if defined .epilog							;SYNTAX
    restore	._after_epilog					;rst_syms_endp2
    .saved_regs_code_size= ._after_epilog - .epilog - 1
    define	._after_epilog	undefined			;rst_syms_endp2
  else									;SYNTAX
    displn	'error: missing "endp"'					;SYNTAX
  end if								;SYNTAX
}

macro	_save_sngl	op {
  if op eqtype eax | op eq flags					;SYNTAX
    displn	'DEBUG(2): saving: '#`op				;DEBUG2
	_pu	op
    .saved_regs_size= .saved_regs_size + last_push_size
    .saved_regs	equ op .saved_regs
  else									;SYNTAX
    displn	'error: can only save registers, memory '# \		;SYNTAX
		'or flags, skipping argument'				;SYNTAX
  end if								;SYNTAX
}
macro	_save_mult	op {
local	m
  if op eqtype dword [ebx] | cat eq dword flags | cat eq word flags \
  | cat eqtype dword eax | op eqtype [ebx]				;SYNTAX
	_pu	op
    .saved_regs_size= .saved_regs_size + last_push_size
    .saved_regs	equ m .saved_regs
    match	all, op \{
    display	'DEBUG(2): saving: '					;DEBUG2
    irps	i, all\\{ display \\`i\\#' ' \\}			;DEBUG2
    displn								;DEBUG2
    define	m	all
    \}
  else									;SYNTAX
    displn	'error: can only save registers, memory '# \		;SYNTAX
		'or flags, skipping argument'				;SYNTAX
  end if
}
macro	save	[arg] {
common
  match	name, .proc_name \{ displn 'DEBUG: saving in: '\#\`name \}	;DEBUG
  restore	.._proc_prolog				;SYNTAX	& rst_syms_endp2
  if $ - .._proc_prolog <> 0
    displn	'error: "save" statement not directly after "proc"'	;SYNTAX
    ;"save" is not allowed in the middle of procedures 'cause neither	;SYNTAX
    ;preprocessor nor assembler would know how often each "save" would	;SYNTAX
    ;actually get executed.						;SYNTAX
    ;They wouldn't know how to change arg_ofs and thus			;SYNTAX
    ;all argument symbols may get broken!				;SYNTAX
  else
    _parse_args	_save_sngl,_save_mult, arg
@@:
    match	name, .proc_name \{ name equ @b \}

    .args_ofs= .args_ofs + .saved_regs_size
  end if
  define	.._proc_prolog	undefined			;rst_syms_endp2
}

macro	_do_epilog {
  match	all, .saved_regs \{
    displn	'DEBUG(2): doing epilog'				;DEBUG2
  irps	i, all \\{
    if i eqtype eax | i eq flags | i eqtype dword [ebx] \
    | i eq dword flags | i eq word flags | i eqtype dword eax | i eqtype [ebx]
	displn	'DEBUG(2): restoring: '\\\#\\\`i			;DEBUG2
	_po	i
    end if
  \\}
  \}
  if .args_size = 0
	ret
  else
	ret	.args_size
  end if
}

macro	return {
  if ~.proc_name eq
    match	name, .proc_name \{					;DEBUG
      displn 'DEBUG: returning from: '\#\`name				;DEBUG
    \}									;DEBUG
    if .saved_regs_code_size > max_code_size_inline_return
      displn 'DEBUG(2): jumping to epilog'				;DEBUG2
	jmp	.epilog
    else
      _do_epilog
    end if
  else									;HINT
    match	=true, show_hints \{					;HINT
      displn 'hint: superfluous "return"'				;HINT
    \}									;HINT
  end if
}

;deletes 1 symbolic name completely
macro	_delete_symb	name {					;rst_syms_endp2
  match	all, name \{						;rst_syms_endp2
  irps	i, all \\{						;rst_syms_endp2
    restore	name						;rst_syms_endp2
  \\}								;rst_syms_endp2
  \}								;rst_syms_endp2
}								;rst_syms_endp2

macro	endp {
  if ~.proc_name eq
    match	name, .proc_name \{ displn 'DEBUG: leaving: '\#\`name \};DEBUG
.epilog:
    _do_epilog
    restore	._after_epilog					;rst_syms_endp2
._after_epilog:
    define	._after_epilog	undefined			;rst_syms_endp2

    match	all, .args \{					;rst_syms_endp
    irps	i, all \\{					;rst_syms_endp
      displn	'DEBUG(2): undefining argument: '\\#\\`i;DEBUG2 &rst_syms_endp
      restore	i						;rst_syms_endp
    \\}								;rst_syms_endp
    \}								;rst_syms_endp
    _delete_symb	.args					;rst_syms_endp2
    _delete_symb	.saved_regs				;rst_syms_endp2
;    .args	equ						;!rst_syms_endp2
;    .saved_regs	equ					;!rst_syms_endp2

    match	name, .proc_name \{ restore name \}		;rst_syms_endp2
    restore	.proc_name					;rst_syms_endp2
    .proc_name	equ
local	tmp							;rst_syms_endp2
tmp:								;rst_syms_endp2
    displn								;DEBUG
  else									;HINT
    match =true, show_hints \{ displn 'hint: superfluous "endp"' \}	;HINT
  end if
}

macro	call	proc,[arg] {
;;;TODO: add optional parameter count checking
common
  if ~ arg eq
reverse
	_pu	arg
common
  end if
	call	proc
}

pword	equ fword
tbyte	equ tword
oword	equ dqword
xword	equ dqword

;some shortcuts, handy for pushing and popping registers
;we must use "fix" because of the commas
mm0..0	fix mm0							;OPTIONAL
mm0..1	fix mm0,mm1						;OPTIONAL
mm0..2	fix mm0,mm1,mm2
mm0..3	fix mm0,mm1,mm2,mm3
mm0..4	fix mm0,mm1,mm2,mm3,mm4
mm0..5	fix mm0,mm1,mm2,mm3,mm4,mm5
mm0..6	fix mm0,mm1,mm2,mm3,mm4,mm5,mm6
mm0..7	fix mm0,mm1,mm2,mm3,mm4,mm5,mm6,mm7
mm1..1	fix mm1							;OPTIONAL
mm1..2	fix mm1,mm2						;OPTIONAL
mm1..3	fix mm1,mm2,mm3
mm1..4	fix mm1,mm2,mm3,mm4
mm1..5	fix mm1,mm2,mm3,mm4,mm5
mm1..6	fix mm1,mm2,mm3,mm4,mm5,mm6
mm1..7	fix mm1,mm2,mm3,mm4,mm5,mm6,mm7
mm2..2	fix mm2							;OPTIONAL
mm2..3	fix mm2,mm3						;OPTIONAL
mm2..4	fix mm2,mm3,mm4
mm2..5	fix mm2,mm3,mm4,mm5
mm2..6	fix mm2,mm3,mm4,mm5,mm6
mm2..7	fix mm2,mm3,mm4,mm5,mm6,mm7
mm3..3	fix mm3							;OPTIONAL
mm3..4	fix mm3,mm4						;OPTIONAL
mm3..5	fix mm3,mm4,mm5
mm3..6	fix mm3,mm4,mm5,mm6
mm3..7	fix mm3,mm4,mm5,mm6,mm7
mm4..4	fix mm4							;OPTIONAL
mm4..5	fix mm4,mm5						;OPTIONAL
mm4..6	fix mm4,mm5,mm6
mm4..7	fix mm4,mm5,mm6,mm7
mm5..5	fix mm5							;OPTIONAL
mm5..6	fix mm5,mm6						;OPTIONAL
mm5..7	fix mm5,mm6,mm7
mm6..6	fix mm6							;OPTIONAL
mm6..7	fix mm6,mm7						;OPTIONAL
mm7..7	fix mm7							;OPTIONAL
mm7..6	fix mm7,mm6						;OPTIONAL
mm7..5	fix mm7,mm6,mm5
mm7..4	fix mm7,mm6,mm5,mm4
mm7..3	fix mm7,mm6,mm5,mm4,mm3
mm7..2	fix mm7,mm6,mm5,mm4,mm3,mm2
mm7..1	fix mm7,mm6,mm5,mm4,mm3,mm2,mm1
mm7..0	fix mm7,mm6,mm5,mm4,mm3,mm2,mm1,mm0
mm6..5	fix mm6,mm5						;OPTIONAL
mm6..4	fix mm6,mm5,mm4
mm6..3	fix mm6,mm5,mm4,mm3
mm6..2	fix mm6,mm5,mm4,mm3,mm2
mm6..1	fix mm6,mm5,mm4,mm3,mm2,mm1
mm6..0	fix mm6,mm5,mm4,mm3,mm2,mm1,mm0
mm5..4	fix mm5,mm4						;OPTIONAL
mm5..3	fix mm5,mm4,mm3
mm5..2	fix mm5,mm4,mm3,mm2
mm5..1	fix mm5,mm4,mm3,mm2,mm1
mm5..0	fix mm5,mm4,mm3,mm2,mm1,mm0
mm4..3	fix mm4,mm3						;OPTIONAL
mm4..2	fix mm4,mm3,mm2
mm4..1	fix mm4,mm3,mm2,mm1
mm4..0	fix mm4,mm3,mm2,mm1,mm0
mm3..2	fix mm3,mm2						;OPTIONAL
mm3..1	fix mm3,mm2,mm1
mm3..0	fix mm3,mm2,mm1,mm0
mm2..1	fix mm2,mm1						;OPTIONAL
mm2..0	fix mm2,mm1,mm0
mm1..0	fix mm1,mm0						;OPTIONAL

xmm0..0	fix xmm0						;OPTIONAL
xmm0..1	fix xmm0,xmm1						;OPTIONAL
xmm0..2	fix xmm0,xmm1,xmm2
xmm0..3	fix xmm0,xmm1,xmm2,xmm3
xmm0..4	fix xmm0,xmm1,xmm2,xmm3,xmm4
xmm0..5	fix xmm0,xmm1,xmm2,xmm3,xmm4,xmm5
xmm0..6	fix xmm0,xmm1,xmm2,xmm3,xmm4,xmm5,xmm6
xmm0..7	fix xmm0,xmm1,xmm2,xmm3,xmm4,xmm5,xmm6,xmm7
xmm1..1	fix xmm1						;OPTIONAL
xmm1..2	fix xmm1,xmm2						;OPTIONAL
xmm1..3	fix xmm1,xmm2,xmm3
xmm1..4	fix xmm1,xmm2,xmm3,xmm4
xmm1..5	fix xmm1,xmm2,xmm3,xmm4,xmm5
xmm1..6	fix xmm1,xmm2,xmm3,xmm4,xmm5,xmm6
xmm1..7	fix xmm1,xmm2,xmm3,xmm4,xmm5,xmm6,xmm7
xmm2..2	fix xmm2						;OPTIONAL
xmm2..3	fix xmm2,xmm3						;OPTIONAL
xmm2..4	fix xmm2,xmm3,xmm4
xmm2..5	fix xmm2,xmm3,xmm4,xmm5
xmm2..6	fix xmm2,xmm3,xmm4,xmm5,xmm6
xmm2..7	fix xmm2,xmm3,xmm4,xmm5,xmm6,xmm7
xmm3..3	fix xmm3						;OPTIONAL
xmm3..4	fix xmm3,xmm4						;OPTIONAL
xmm3..5	fix xmm3,xmm4,xmm5
xmm3..6	fix xmm3,xmm4,xmm5,xmm6
xmm3..7	fix xmm3,xmm4,xmm5,xmm6,xmm7
xmm4..4	fix xmm4						;OPTIONAL
xmm4..5	fix xmm4,xmm5						;OPTIONAL
xmm4..6	fix xmm4,xmm5,xmm6
xmm4..7	fix xmm4,xmm5,xmm6,xmm7
xmm5..5	fix xmm5						;OPTIONAL
xmm5..6	fix xmm5,xmm6						;OPTIONAL
xmm5..7	fix xmm5,xmm6,xmm7
xmm6..6	fix xmm6						;OPTIONAL
xmm6..7	fix xmm6,xmm7						;OPTIONAL
xmm7..7	fix xmm7						;OPTIONAL
xmm7..6	fix xmm7,xmm6						;OPTIONAL
xmm7..5	fix xmm7,xmm6,xmm5
xmm7..4	fix xmm7,xmm6,xmm5,xmm4
xmm7..3	fix xmm7,xmm6,xmm5,xmm4,xmm3
xmm7..2	fix xmm7,xmm6,xmm5,xmm4,xmm3,xmm2
xmm7..1	fix xmm7,xmm6,xmm5,xmm4,xmm3,xmm2,xmm1
xmm7..0	fix xmm7,xmm6,xmm5,xmm4,xmm3,xmm2,xmm1,xmm0
xmm6..5	fix xmm6,xmm5						;OPTIONAL
xmm6..4	fix xmm6,xmm5,xmm4
xmm6..3	fix xmm6,xmm5,xmm4,xmm3
xmm6..2	fix xmm6,xmm5,xmm4,xmm3,xmm2
xmm6..1	fix xmm6,xmm5,xmm4,xmm3,xmm2,xmm1
xmm6..0	fix xmm6,xmm5,xmm4,xmm3,xmm2,xmm1,xmm0
xmm5..4	fix xmm5,xmm4						;OPTIONAL
xmm5..3	fix xmm5,xmm4,xmm3
xmm5..2	fix xmm5,xmm4,xmm3,xmm2
xmm5..1	fix xmm5,xmm4,xmm3,xmm2,xmm1
xmm5..0	fix xmm5,xmm4,xmm3,xmm2,xmm1,xmm0
xmm4..3	fix xmm4,xmm3						;OPTIONAL
xmm4..2	fix xmm4,xmm3,xmm2
xmm4..1	fix xmm4,xmm3,xmm2,xmm1
xmm4..0	fix xmm4,xmm3,xmm2,xmm1,xmm0
xmm3..2	fix xmm3,xmm2						;OPTIONAL
xmm3..1	fix xmm3,xmm2,xmm1
xmm3..0	fix xmm3,xmm2,xmm1,xmm0
xmm2..1	fix xmm2,xmm1						;OPTIONAL
xmm2..0	fix xmm2,xmm1,xmm0
xmm1..0	fix xmm1,xmm0						;OPTIONAL
;this is supposed to be 32bit only macros, so we don't have xmm8..15 ;(
;is there any need for shortcuts to all GPRs? I don't considering defining those
;because of the myriad of different combinations out there

;TEST START
macro	_chk_proc {
  macro	_do_proc \{
  \local	dat, some_proc, some_other_proc, something
    dat	dd 0

    define	max_code_size_inline_return	0

    proc	some_proc
    save	eax
    return
    endp

    define	max_code_size_inline_return	1

    proc	some_other_proc
    save	eax
    return
    endp
	call	something

    proc	something, a,b,c
    save	eax,flags,mm1..5,xmm6..2,mm4,xmm7,\
		dqword [esi+ecx*8+100],dword [dat],dword [.b]
	mov	eax,[.a]
	jz	.epilog
	mov	eax,[.b]
	mov	eax,[.c]
    return
	dd	.args_size
	_pu	eax
	.args_ofs= .args_ofs+4
	jne	something
	mov	eax,[.a]
	mov	eax,[.b]
	mov	eax,[.c]
    endp
	call	something
    restore	max_code_size_inline_return
    restore	max_code_size_inline_return
  \}
  define	show_hints		false
  define	sse_mov			movups

  define	native_pushpop		true
  define	preserve_stack_flags	false
  _do_proc
  define	preserve_stack_flags	true
  _do_proc
  define	native_pushpop		false
  _do_proc

  restore	native_pushpop
  restore	preserve_stack_flags
  restore	preserve_stack_flags
  restore	native_pushpop
  restore	sse_mov
  restore	show_hints
  purge	_do_proc
}

;uncomment the following line to test "proc", "save", "return" and "endp" macros
chk_code	_chk_proc,0x56E1A61EDB33FF87
;END TEST

macro	_push	[arg] {
common
  _parse_args	_pu,_pu, arg
}

macro	_pop	[arg] {
common
  _parse_args	_po,_po, arg
}

;we can't just call the above macros "push" and "pop", or else we will get
;circular macro references if you do something like
;
;proc	whatever
;save	something
;endp
push	fix	_push
pop	fix	_pop
;;;TODO: add pushd, pushw, popd and popw macros

;TEST	START
macro	_chk_pushpop {
local	dat
dat	dq 0xFEDCBA9876543210
  define	native_pushpop		true
  define	preserve_stack_flags	true
  define	sse_mov			movups
	push	eax,dat,dword dat,0.0,qword 0.0,\
		dword [dat+8*ebx+edi],qword [dat]
	push	eax dat dword dat 0.0 qword 0.0 \
		dword [dat+8*ebx+edi] qword [dat]
	pop	eax,dword [dat+8*ebx+edi],qword [dat]
	pop	eax dword [dat+8*ebx+edi] qword [dat]
  restore	sse_mov
  restore	preserve_stack_flags
  restore	native_pushpop
}

;uncomment the following line to test the final "push" and "pop" macros
chk_code	_chk_pushpop,0xC99630834567F7D5
;END	TEST
