;;;;;;;;;;;;;;;; FPU INSTRUCTIONS ;;;;;;;;;;;;;;;;

; WRITTEN BY CODESTAR

numeric st, st0=0, st1, st2,\
  st3, st4, st5, st6, st7

irp <name,a,b,c>,\
  fclex,$9B,$DB,$E2, finit,$9B,$DB,$E3
  macro name
    db a, b, c
  end macro
end irp

irp <name,a,b>,\
  f2xm1,$D9,$F0, fabs,$D9,$E1,\
  fchs,$D9,$E0, fnclex,$DB,$E2,\
  fcompp,$DE,$D9, fcos,$D9,$FF,\
  fdecstp,$D9,$F6, femms,$0F,$0E,\
  fincstp,$D9,$F7, fninit,$DB,$E3,\
  fld1,$D9,$E8, fldl2e,$D9,$EA,\
  fldl2t,$D9,$E9, fldlg2,$D9,$EC,\
  fldln2,$D9,$ED, fldpi,$D9,$EB,\
  fldz,$D9,$EE, fnop,$D9,$D0,\
  fpatan,$D9,$F3, fptan,$D9,$F2,\
  fprem,$D9,$F8, fprem1,$D9,$F5,\
  frndint,$D9,$FC, fscale,$D9,$FD,\
  fsin,$D9,$FE, fsincos,$D9,$FB,\
  fsqrt,$D9,$FA
  macro name
    db a, b
  end macro
end irp

; return size of type name

macro fpu_get_size size, type
  size=0
  match =word, type
    size='2'
  else match =dword, type
    size='4'
  else match =qword, type
    size='8'
  else match =tword, type
    size='t'
  else
    error `type, ' - Invalid type'
  end match
end macro

; given operand, return type='r' (stx),
; 'm', 'm2', 'm4', 'm8', 'mt'

macro fpu_get_type type, p
  local n, is, size
  n=0
  is=0
  type=0
  size=0
  match t[m], p
    fpu_get_size size, t
    if size='2'
      type='m2'
    else if size='4'
      type='m4'
    else if size='8'
      type='m8'
    else if size='t'
      type='mt'
    end if
    get_mode_m m
  else match [m], p
    type='m'
    get_mode_m m
    its_size=0
  else match a, p
    name_length a, n
    if n=2
      match =st, a ; st0
        type='r'
      end match
    else if n=3
      is_in is, a,\
        st0, st1, st2, st3,\
        st4, st5, st6, st7
      if is
        type='r' ; stx
      end if
    end if
  else
    error 'Operand/s expected'
  end match
end macro

; return mode = 'r0'=st0, 'rx'=stx,
; 'r0rx'=st0,stx, 'rxr0'=stx,st0,
; 'm2/4/8/t'=m16/32/64/80

macro fpu_get_mode mode, p&
  local n, s, is, type1, type2
  n=0
  s=0
  is=0
  mode=0
  match a=,b, p
    fpu_get_type type1, a
    fpu_get_type type2, b
    if type1='r' & type2='r'
      if a=0
        mode='r0rx'
        its_index=b
      else
        mode='rxr0'
        its_index=a
      end if
    else
      error `a, ',', `b, ' - ',\
        'Invalid operands'
    end if
  else match a, p
    fpu_get_type mode, p
    if mode=0
      error `p, ' - Invalid operand'
    end if
    if mode='r'
      if a=0
        mode='r0'
      else
        mode='rx'
      end if
    end if
  end match
end macro

;;;;;;;;;;;;;;;;;; LOAD, STORE ;;;;;;;;;;;;;;;;;;;

; fld, fst/p...

; fld stx   = $D9 C0+r
; fld m32   = $D9 /0
; fld m64   = $DD /0
; fld m80   = $DB /5

macro fld x
  local type
  type=0
  fpu_get_type type, x
  if type='r'
    db $D9, $C0+x
  else if type='m4'
    db $D9
    write_mode 0
  else if type='m8'
    db $DD
    write_mode 0
  else if type='mt'
    db $DB
    write_mode 5
  else
    error `x, ' - Invalid operand/s'
  end if
end macro

; fst stx   = $DD $D0+r
; fst m32   = $D9 /2
; fst m64   = $DD /2

macro fst x
  local type
  type=0
  fpu_get_type type, x
  if type='r'
    db $DD, $D0+x
  else if type='m4'
    db $D9
    write_mode 2
  else if type='m8'
    db $DD
    write_mode 2
  else
    error `x, ' - Invalid operand/s'
  end if
end macro

; fstp stx  = $DD $D8+r
; fstp m32  = $D9 /3
; fstp m64  = $DD /3
; fstp m80  = $DB /7

macro fstp x
  local type
  type=0
  fpu_get_type type, x
  if type='r'
    db $DD, $D8+x
  else if type='m4'
    db $D9
    write_mode 3
  else if type='m8'
    db $DD
    write_mode 3
  else if type='mt'
    db $DB
    write_mode 7
  else
    error `x, '- Invalid operand/s'
  end if
end macro

; fbld m80  = $DF /4
; fbstp m80 = $DF /6

irp <name,digit>, fbld,4, fbstp,6
  macro name x
    local type
    type=0
    fpu_get_type type, x
    if type='mt'
      db $DF
      write_mode digit
    else
      error `x, ' - Invalid operand'
    end if
  end macro
end irp

; fldcw m16 = $D9 /5

macro fldcw x
  local type
  type=0
  fpu_get_type type, x
  if type='m2'
    db $D9
    write_mode 5
  else
    error `x, ' - Invalid operand'
  end if
end macro

; fldenv m = $D9 /4
; fnsave m = $DD /6
; frstor m = $DD /4

irp <name,opcode,digit>,\
  fldenv,$D9,4, fnsave,$DD,6, frstor,$DD,4
  macro name x
    local type
    type=0
    fpu_get_type type, x
    if type='m' | type='m4'
      db opcode
      write_mode digit
    else
      error `x, ' - Invalid operand'
    end if
  end macro
end irp

; fsave m = $9B $DD /6

macro fsave x
  local type
  type=0
  fpu_get_type type, x
  if type='m'
    db $9B, $DD
    write_mode 6
  else
    error `x, ' - Invalid operand'
  end if
end macro

; ffree stx = $DD $C0+r

macro ffree x
  local type
  type=0
  fpu_get_type type, x
  if type='r'
    db $DD, $C0+x
  else
    error `x, ' - Invalid operand'
  end if
end macro

;;;;;;;;;;;;;; INTEGER LOAD, STORE ;;;;;;;;;;;;;;;

; fild m16 = $DF /0
; fild m32 = $DB /0
; fild m64 = $DF /5

macro fild x
  local type
  type=0
  fpu_get_type type, x
  if type='m2'
    db $DF
    write_mode 0
  else if type='m4'
    db $DB
    write_mode 0
  else if type='m8'
    db $DF
    write_mode 5
  else
    error `x, ' - Invalid operand'
  end if
end macro

; fist m16 = $DF /2
; fist m32 = $DB /2

macro fist x
  local type
  type=0
  fpu_get_type type, x
  if type='m2'
    db $DF
    write_mode 2
  else if type='m4'
    db $DB
    write_mode 2
  else
    error `x, ' - Invalid operand'
  end if
end macro

; fistp m16 = $DF /3
; fistp m32 = $DB /3
; fistp m64 = $DF /7

macro fistp x
  local type
  type=0
  fpu_get_type type, x
  if type='m2'
    db $DF
    write_mode 3
  else if type='m4'
    db $DB
    write_mode 3
  else if type='m8'
    db $DF
    write_mode 7
  else
    error `x, ' - Invalid operand'
  end if
end macro

;;;;;;;;;;;;;;;;;;; ARITHMETIC ;;;;;;;;;;;;;;;;;;;

; fadd stx      = $D8 $C0+r
; fadd st0,stx  = $D8 $C0+r
; fadd stx,st0  = $DC $C0+r
; fadd m32      = $D8 /0
; fadd m64      = $DC /0
; fmul stx      = $D8 $C8+r
; fmul st0,stx  = $D8 $C8+r
; fmul stx,st0  = $DC $C8+r
; fmul m32      = $D8 /1
; fmul m64      = $DC /1
; fdiv stx      = $D8 $F0+r
; fdiv st0,stx  = $D8 $F0+r
; fdiv stx,st0  = $DC $F8+r
; fdiv m32      = $D8 /6
; fdiv m64      = $DC /6
; fdivr stx     = $D8 $F8+r
; fdivr st0,stx = $D8 $F8+r
; fdivr stx,st0 = $DC $F0+r
; fdivr m32     = $D8 /7
; fdivr m64     = $DC /7

irp <name,opcode,opcode2,digit>,\
  fadd,$C0,$C0,0, fmul,$C8,$C8,1,\
  fdiv,$F0,$F8,6, fdivr,$F8,$F0,7
  macro name p&
    local r, mode
    r=0
    mode=0
    fpu_get_mode mode, p
    r=its_index
    if mode='r0'         ; st0
      db $D8, opcode+0
    else if mode='rx'    ; stx
      db $D8, opcode+r
    else if mode='r0rx'  ; st0, stx
      db $D8, opcode+r
    else if mode='rxr0'  ; stx, st0
      db $DC, opcode2+r
    else if mode='m4'    ; dword [m]
      db $D8
      write_mode digit
    else if mode='m8'    ; qword [m]
      db $DC
      write_mode digit
    else
      error `p, ' - Invalid mode'
    end if
  end macro
end irp

; faddp stx      = $DE C0+r
; faddp stx,st0  = $DE C0+r
; fmulp stx      = $DE C8+r
; fmulp stx,st0  = $DE C8+r
; fdivp stx      = $DE F8+r
; fdivp stx,st0  = $DE F8+r
; fdivrp stx     = $DE F0+r
; fdivrp stx,st0 = $DE F0+r

irp <name,opcode>,\
  faddp,$C0, fmulp,$C8,\
  fdivp,$F8, fdivrp,$F0
  macro name p&
   local r, mode
    r=0
    mode=0
    fpu_get_mode mode, p
    r=its_index
    if mode='r0' | mode='rx' \
      | mode='rxr0'
      db $DE, opcode+r
    else
      error `p, ' - Invalid mode'
    end if
  end macro
end irp

;;;;;;;;;;;;; ARITHMETIC W/ INTEGER ;;;;;;;;;;;;;;

; fiadd m16  = $DE /0
; fiadd m32  = $DA /0
; fimul m16  = $DE /1
; fimul m32  = $DA /1
; fisub m16  = $DE /4
; fisub m32  = $DA /4
; fisubr m16 = $DE /5
; fisubr m32 = $DA /5
; fidiv m16  = $DE /6
; fidiv m32  = $DA /6
; fidivr m16 = $DE /7
; fidivr m32 = $DA /7

irp <name,digit>,\
  fiadd,0, fimul,1, fisub,4,\
  fisubr,5, fidiv,6, fidivr,7
  macro name p
    local size
    size=0
    match t[m], p
      fpu_get_size size, t
      if size='2'
        db $DE
      else if size='4'
        db $DA
      else
        error 'Invalid size'
      end if
      get_mode_m m
      write_mode digit
    else
      error 'Invalid operand'
    end match
  end macro
end irp

;;;;;;;;;;;;;;;;;;;; COMPARE ;;;;;;;;;;;;;;;;;;;;;

; fcom stx      = $D8 $D0+r
; fcom st0,stx  = $D8 $D0+r
; fcom m32      = $D8 /2
; fcom m64      = $DC /2
; fcomp stx     = $D8 $D8+r
; fcomp st0,stx = $D8 $D8+r
; fcomp m32     = $D8 /3
; fcomp m64     = $DC /3

irp <name,opcode,digit>,\
  fcom,$D0,2, fcomp,$D8,3
  macro name p&
    local r, mode
    r=0
    mode=0
    fpu_get_mode mode, p
    r=its_index
    if mode='r0'         ; st0
      db $D8, opcode+0
    else if mode='rx'    ; stx
      db $D8, opcode+r
    else if mode='r0rx'  ; st0, stx
      db $D8, opcode+r
    else if mode='m4'    ; dword [m]
      db $D8
      write_mode digit
    else if mode='m8'    ; qword [m]
      db $DC
      write_mode digit
    else
      error `p, ' - Invalid mode'
    end if
  end macro
end irp

; fcomi stx      = $DB $F0+r
; fcomi st0,stx  = $DB $F0+r
; fcomip stx     = $DF $F0+r
; fcomip st0,stx = $DF $F0+r

irp <name,opcode>, fcomi,$DB, fcomip,$DF
  macro name p&
   local r, mode
    r=0
    mode=0
    fpu_get_mode mode, p
    r=its_index
    if mode='r0' | mode='rx' \
      | mode='r0rx'
      db opcode, $F0+r
    else
      error `p, ' - Invalid mode'
    end if
  end macro
end irp

; ficom m16  = $DE /2
; ficom m32  = $DA /2
; ficomp m16 = $DE /3
; ficomp m32 = $DA /3

irp <name,digit>, ficom,2, ficomp,3
  macro name p&
    local size
    size=0
    match t[m], p
      fpu_get_size size, t
      if size='2'
        db $DE
      else if size='4'
        db $DA
      else
        error 'Invalid size'
      end if
      get_mode_m m
      write_mode digit
    else
      error 'Invalid operand'
    end match
  end macro
end irp

;;;;;;;;;;;;;;;;;;;;; FCMOVX ;;;;;;;;;;;;;;;;;;;;;

; fcmovb stx       = $DA $C0+r
; fcmovb st0,stx   = $DA $C0+r
; fcmove stx       = $DA $C8+r
; fcmove st0,stx   = $DA $C8+r
; fcmovbe stx      = $DA $D0+r
; fcmovbe st0,stx  = $DA $D0+r
; fcmovu stx       = $DA $D8+r
; fcmovu st0,stx   = $DA $D8+r
; fcmovnb stx      = $DB $C0+r
; fcmovnb st0,stx  = $DB $C0+r
; fcmovne stx      = $DB $C8+r
; fcmovne st0,stx  = $DB $C8+r
; fcmovnbe stx     = $DB $D0+r
; fcmovnbe st0,stx = $DB $D0+r
; fcmovnu stx      = $DB $D8+r
; fcmovnu st0,stx  = $DB $D8+r

irp <name,opcode,opcode2>,\
  fcmovb,$DA,$C0, fcmove,$DA,$C8,\
  fcmovbe,$DA,$D0, fcmovu,$DA,$D8,\
  fcmovnb,$DB,$C0, fcmovne,$DB,$C8,\
  fcmovnbe,$DB,$D0, fcmovnu,$DB,$D8
  macro name p&
   local r, mode
    r=0
    mode=0
    fpu_get_mode mode, p
    r=its_index
    if mode='r0' | mode='rx' \
      | mode='r0rx'
      db opcode, opcode2+r
    else
      error `p, ' - Invalid mode'
    end if
  end macro
end irp

;;;;;;;;;;;;;;;;;;;; TESTING ;;;;;;;;;;;;;;;;;;;;;

macro test_fpu_load_store
  fld st1
  fld dword [edi]
  fld qword [eax+ecx*8]
  fld tword [$CAFEBABE]
  fst st3
  fst dword [$12345678]
  fst qword [$ABCD1234]
  fstp st7
  fstp dword [ebx+edx*4]
  fstp qword [edi]
  fstp tword [$CAFEBABE]
  fbld tword [edi]
  fbstp tword [ebx]
  fild word [eax]
  fild dword [ecx]
  fild qword [edx]
  fist word [eax+ecx*2]
  fist dword [esi+$BABE1234]
  fldenv [eax]
  fnsave [ecx]
  frstor [edx]
  ffree st7
end macro

macro test_fpu_arithmetic
  fadd st1
  fadd st1,st0
  fadd dword [$12345678]
  fadd qword [$ABCD1234]
  fmul st1
  fmul st1,st0
  fmul dword [$12345678]
  fmul qword [$ABCD1234]
  fdiv st3
  fdiv st0,st7
  fdiv st7,st0
  fdiv dword [eax+4]
  fdiv qword [ecx+8]
  fdivr st3
  fdivr st0,st7
  fdivr st7,st0
  fdivr dword [eax+4]
  fdivr qword [ecx+$CAFEBABE]
  faddp st1
  faddp st2,st0
  fmulp st3
  fmulp st4,st0
  fdivp st5
  fdivp st7,st0
  fdivrp st1
  fdivrp st2,st0
  fiadd word [eax]
  fiadd dword [ecx]
  fimul word [edx]
  fimul dword [ebx]
  fisub word [eax]
  fisub dword [ecx]
  fisubr word [edx]
  fisubr dword [ebx]
  fidiv word [eax]
  fidiv dword [ecx]
  fidivr word [edx]
  fidivr dword [ebx]
end macro

macro test_fpu_compare
  fcom st1
  fcom st0,st7
  fcom dword [eax]
  fcom qword [ecx]
  fcomp st2
  fcomp st0,st3
  fcomp dword [eax]
  fcomp qword [ecx]
  fcomi st1
  fcomi st0,st2
  fcomip st3
  fcomip st0,st4
  ficom word [eax]
  ficom dword [ecx]
  ficomp word [eax]
  ficomp dword [edi+$CAFEBABE]
end macro

macro test_fpu_cmove
  fcmovb st1
  fcmovb st0,st2
  fcmove st3
  fcmove st0,st4
  fcmovbe st5
  fcmovbe st0,st6
  fcmovu st7
  fcmovu st0,st1
  fcmovnb st2
  fcmovnb st0,st3
  fcmovne st4
  fcmovne st0,st5
  fcmovnbe st6
  fcmovnbe st0,st7
  fcmovnu st1
  fcmovnu st0,st2
end macro

macro test_fpu
  test_fpu_load_store
  test_fpu_arithmetic
  test_fpu_compare
  test_fpu_cmove
end macro