{$ifdef LINUX}
{$include ../defines.pas}
{$else}
{$include ..\defines.pas}
{$endif}
unit fpu_entry;

interface

{$include status_w.pas}

uses service_fpu, fpu_types, fpu_system, errors, fpu_aux, reg_compare,
  fpu_etc, reg_constant, fpu_trig;

const
  st_instr_table: array[0..63] of procedure = (
    fadd__, fld_i_, FPU_illegal, FPU_illegal, fadd_i, ffree_, faddp_, FPU_illegal,
    fmul__, fxch_i, FPU_illegal, FPU_illegal, fmul_i, FPU_illegal,
    fmulp_, FPU_illegal,
    fcom_st, fp_nop, FPU_illegal, FPU_illegal, FPU_illegal, fst_i_,
    FPU_illegal, FPU_illegal,
    fcompst, FPU_illegal, FPU_illegal, FPU_illegal, FPU_illegal,
    fstp_i, fcompp, FPU_illegal,
    fsub__, FPU_etc_, FPU_illegal, finit_, fsubri, fucom_, fsubrp, fstsw_,
    fsubr_, fconst, fucompp, FPU_illegal, fsub_i, fucomp, fsubp_, FPU_illegal,
    fdiv__, FPU_triga, FPU_illegal, FPU_illegal, fdivri, FPU_illegal,
    fdivrp, FPU_illegal,
    fdivr_, FPU_trigb, FPU_illegal, FPU_illegal, fdiv_i, FPU_illegal,
    fdivp_, FPU_illegal);

const
  _NONE_ = 0;   { Take no special action }

const
  _REG0_ = 1;   { Need to check for not empty st(0) }

const
  _REGI_ = 2;   { Need to check for not empty st(0) and st(rm) }

const
  _REGi2_ = 0;   { Uses st(rm) }

const
  _PUSH_ = 3;   { Need to check for space to push onto stack }

const
  _null_ = 4;   { Function illegal or not implemented }

const
  _REGIi = 5;   { Uses st(0) and st(rm), result to st(rm) }

const
  _REGIp = 6;   { Uses st(0) and st(rm), result to st(rm) then pop }

const
  _REGIc = 0;   { Compare st(0) and st(rm) }

const
  _REGIn = 0;   { Uses st(0) and st(rm), but handle checks later }

const
  type_table_2: array[0..63] of u_char = (
    _REGI_, _NONE_, _null_, _null_, _REGIi, _REGi_, _REGIp, _REGi_,
    _REGI_, _REGIn, _null_, _null_, _REGIi, _REGI_, _REGIp, _REGI_,
    _REGIc, _NONE_, _null_, _null_, _REGIc, _REG0_, _REGIc, _REG0_,
    _REGIc, _REG0_, _null_, _null_, _REGIc, _REG0_, _REGIc, _REG0_,
    _REGI_, _NONE_, _null_, _NONE_, _REGIi, _REGIc, _REGIp, _NONE_,
    _REGI_, _NONE_, _REGIc, _null_, _REGIi, _REGIc, _REGIp, _null_,
    _REGI_, _NONE_, _null_, _null_, _REGIi, _null_, _REGIp, _null_,
    _REGI_, _NONE_, _null_, _null_, _REGIi, _null_, _REGIp, _null_
    );

procedure math_emulate2 (addr_modes: fpu_addr_modes; FPU_modrm: u_char;
  byte1: u_char; data_address: longword; data_sel_off: address; entry_sel_off: address);

{$include fpu_const.pas}

implementation

uses wmfpuemu_Glue, fpu_tags, reg_divide, reg_ld_str, reg_add_sub,
  reg_mul, load_store, Config;

procedure math_emulate2 (addr_modes: fpu_addr_modes; FPU_modrm: u_char;
  byte1: u_char; data_address: longword; data_sel_off: address; entry_sel_off: address);
  var
    code: u16;
    unmasked: intg;
    loaded_data: FPU_REG;
    st0_ptr: PFPU_REG;
    loaded_tag, st0_tag: u_char;
    status1: u16;
    instr_index: u_char;
    MyAddr: longword;
  label
    do_the_FPU_interrupt, FPU_fwait_done, reg_mem_instr_done, FPU_instruction_done;
  begin

    // assuming byte is $d8..$df or $db=FWAIT

    // lock is not a valid prefix for FPU instructions, +++
    // let the cpu handle it to generate a SIGILL.


    I387.soft.no_update := 0;

    if (byte1 = FWAIT_OPCODE) then
      begin
      if (I387.soft.swd and SW_Summary) <> 0 then
        goto do_the_FPU_interrupt
      else
        goto FPU_fwait_done;
      end;

    if (I387.soft.swd and SW_Summary) <> 0 then
      begin
    (* Ignore the error for now if the current instruction is a no-wait
       control instruction *)
    (* The 80486 manual contradicts itself on this topic,
       but a real 80486 uses the following instructions:
       fninit, fnstenv, fnsave, fnstsw, fnstenv, fnclex.
       *)
      code := (FPU_modrm shl 8) or byte1;
      if (((((code and $f803) = $e003) or    (* fnclex, fninit, fnstsw *)
        (((code and $3003) = $3001) and   (* fnsave, fnstcw, fnstenv,
                                                   fnstsw *)
        ((code and $c000) <> $c000)))) = False) then
        begin
      (*
       *  We need to simulate the action of the kernel to FPU
       *  interrupts here.
       *)
        do_the_FPU_interrupt:

          math_abort(nil, 0);
        exit;
        end;
      end;

    entry_sel_off.opcode := ((byte1 shl 8) or FPU_modrm) and $7FF;

    pu_char(@I387.soft.rm)^ := FPU_modrm and 7;

    if (FPU_modrm < 192) then
      begin
      (* All of these instructions use the mod_/rm byte to get a data address *)

      if ((byte1 and 1) = 0) then
        begin
        status1 := I387.soft.swd;

        st0_ptr := st(0);
        st0_tag := FPU_gettag0();

        (* Stack underflow has priority *)
        if (st0_tag xor TAG_Empty) <> 0 then
          begin
          if (addr_modes.default_mode and PROTECTED_) <> 0 then
            begin
            (* This table works for 16 and 32 bit protected mod_e *)
            if (I387.soft.alimit < data_sizes_16[(byte1 shr 1) and 3]) then
              begin
              math_abort(nil, 0);
              exit;
              end;
            end;

          unmasked := 0;  (* Do this here to stop compiler warnings. *)
          case ((byte1 shr 1) and 3) of
            0:
              begin
              unmasked := FPU_load_single(pfloat(Data_Address), @loaded_data);
              loaded_tag := unmasked and $ff;
              unmasked := unmasked and not $ff;
              end;
            1:
              begin
              loaded_tag := FPU_load_int32(ps32(Data_Address), @loaded_data);
              // bbd: was (u32 *)
              end;
            2:
              begin
              unmasked := FPU_load_double(pdouble(Data_Address), @loaded_data);
              loaded_tag := unmasked and $ff;
              unmasked := unmasked and not $ff;
              end;
            3:
              begin
              loaded_tag := FPU_load_int16(ps16(Data_Address), @loaded_data);
              end;
            else  (* Used here to suppress gcc warnings. *)
              begin
              loaded_tag := FPU_load_int16(ps16(Data_Address), @loaded_data);
              end;
            end;

              (* No more access to user memory, it is safe
                 to use static data now *)

          (* NaN operands have the next priority. *)
              (* We have to delay looking at st(0) until after
                 loading the data, because that data might contain an SNaN *)
          if ((st0_tag = TAG_Special) and (isNaN(st0_ptr) <> 0)) or
            ((loaded_tag = TAG_Special) and (isNaN(@loaded_data) <> 0)) then
            begin
                  (* Restore the status word; we might have loaded a
                     denormal. *)
            I387.soft.swd := status1;
            if ((FPU_modrm and $30) = $10) then
              begin
              (* fcom or fcomp *)
              fpu_EXCEPTION(EX_Invalid);
              setcc(SW_C3 or SW_C2 or SW_C0);
              if ((FPU_modrm and $08) <> 0 and
                (I387.soft.cwd and CW_Invalid)) then
                FPU_pop();             (* fcomp, masked, so we pop. *)
              end
            else
              begin
              if (loaded_tag = TAG_Special) then
                loaded_tag := FPU_Special(@loaded_data);
{$ifdef PECULIAR_486}
                      (* This is not really needed, but gives behaviour
                         identical to an 80486 *)
                      if ( (FPU_modrm  and $28) = $20 ) then
                        (* fdiv or fsub *)
                        real_2op_NaN(@loaded_data, loaded_tag, 0, @loaded_data)
                      else
{$endif}      (* PECULIAR_486 *)
              (* fadd, fdivr, fmul, or fsubr *)
              real_2op_NaN(@loaded_data, loaded_tag, 0, st0_ptr);
              end;
            goto reg_mem_instr_done;
            end;

          if ((unmasked <> 0) and (((FPU_modrm and $30) = $10) = False)) then
            begin
            (* Is not a comparison instruction. *)
            if ((FPU_modrm and $38) = $38) then
              begin
              (* fdivr *)
              if ((st0_tag = TAG_Zero) and
                ((loaded_tag = TAG_Valid) or ((loaded_tag = TAG_Special) and
                (exponent(@loaded_data) = EXP_BIAS + EXP_UNDER)))) then
                begin
                if (FPU_divide_by_zero(0, getsign(@loaded_data)) < 0) then
                  begin
                              (* We use the fact here that the unmasked
                                 exception in the loaded data was for a
                                 denormal operand *)
                  (* Restore the state of the denormal op bit *)
                  I387.soft.swd :=
                    I387.soft.swd and not SW_Denorm_Op;
                  I387.soft.swd :=
                    I387.soft.swd or status1 and SW_Denorm_Op;
                  end
                else
                  setsign(st0_ptr, getsign(@loaded_data));
                end;
              end;
            goto reg_mem_instr_done;
            end;

          case ((FPU_modrm shr 3) and 7) of
            0:         (* fadd *)
              begin
              clear_C1();
              FPU_add(@loaded_data, loaded_tag, 0, I387.soft.cwd);
              end;
            1:         (* fmul *)
              begin
              clear_C1();
              FPU_mul(@loaded_data, loaded_tag, 0, I387.soft.cwd);
              end;
            2:         (* fcom *)
              begin
              FPU_compare_st_data(@loaded_data, loaded_tag);
              end;
            3:         (* fcomp *)
              begin
              // bbd: used to typecase to int first, but this corrupted the
              // pointer on 64 bit machines.
              if ((FPU_compare_st_data(@loaded_data, loaded_tag) = 0) and
                (unmasked = 0)) then
                FPU_pop();
              end;
            4:         (* fsub *)
              begin
              clear_C1();
              FPU_sub(LOADED or loaded_tag, @loaded_data, I387.soft.cwd);
              end;
            5:         (* fsubr *)
              begin
              clear_C1();
              FPU_sub(REV or LOADED or loaded_tag, @loaded_data, I387.soft.cwd);
              end;
            6:         (* fdiv *)
              begin
              clear_C1();
              FPU_div(LOADED or loaded_tag, @loaded_data, I387.soft.cwd);
              end;
            7:         (* fdivr *)
              begin
              clear_C1();
              if (st0_tag = TAG_Zero) then
                I387.soft.swd := status1;  (* Undo any denorm tag,
                                                  zero-divide has priority. *)
              FPU_div(REV or LOADED or loaded_tag, @loaded_data, I387.soft.cwd);
              end;
            end;
          end
        else
          begin
          if ((FPU_modrm and $30) = $10) then
            begin
            (* The instruction is fcom or fcomp *)
            fpu_EXCEPTION(EX_StackUnder);
            setcc(SW_C3 or SW_C2 or SW_C0);
            if (((FPU_modrm and $08) <> 0) and
              ((I387.soft.cwd and CW_Invalid) <> 0)) then
              FPU_pop();             (* fcomp *)
            end
          else
            FPU_stack_underflow();
          end;
        reg_mem_instr_done:
          paddress(@I387.soft.foo)^ := data_sel_off;
        end
      else
        begin
        I387.soft.no_update :=
          FPU_load_store(((FPU_modrm and $38) or (byte1 and 6)) shr
          1, addr_modes, Pointer(Data_Address));
        if I387.soft.no_update = 0 then
          begin
          paddress(@I387.soft.foo)^ := data_sel_off;
          end;
        end;
      end
    else
      begin
      (* None of these instructions access user memory *)
      instr_index := (FPU_modrm and $38) or (byte1 and 7);

{$ifdef PECULIAR_486}
      (* This is supposed to be undefined, but a real 80486 seems
         to do this: *)
      paddress(@I387.soft.foo)^.offset := 0;
      paddress(@I387.soft.foo)^.selector := fpu_get_ds;
{$endif}(* PECULIAR_486 *)

      st0_ptr := st(0);
      st0_tag := FPU_gettag0();
      case (type_table_2[instr_index]) of
        _NONE_:   (* also _REGIc: _REGIn *)
          begin
          end;
        _REG0_:
          begin
          if (st0_tag xor TAG_Empty) = 0 then
            begin
            FPU_stack_underflow();
            goto FPU_instruction_done;
            end;
          end;
        _REGIi:
          begin
          if (((st0_tag xor TAG_Empty) = 0) or (FPU_empty_i(FPU_rm^) <> 0)) then
            begin
            FPU_stack_underflow_i(FPU_rm^);
            goto FPU_instruction_done;
            end;
          end;
        _REGIp:
          begin
          if (((st0_tag xor TAG_Empty) = 0) or (FPU_empty_i(FPU_rm^) <> 0)) then
            begin
            FPU_stack_underflow_pop(FPU_rm^);
            goto FPU_instruction_done;
            end;
          end;
        _REGI_:
          begin
          if (((st0_tag xor TAG_Empty) = 0) or (FPU_empty_i(FPU_rm^) <> 0)) then
            begin
            FPU_stack_underflow();
            goto FPU_instruction_done;
            end;
          end;
        _PUSH_:     (* Only used by the fld st(i) instruction *)
          begin
          end;
        _null_:
          begin
          FPU_illegal();
          goto FPU_instruction_done;
          end;
        else
          begin
          fpu_EXCEPTION(EX_INTERNAL or $111);
          goto FPU_instruction_done;
          end;
        end;
      st_instr_table[instr_index];

      FPU_instruction_done: ;
      end;

    if (no_ip_update) = 0 then
      I387.soft.fip := integer(@entry_sel_off);

    FPU_fwait_done: ;

  end;

end.
