ladybird/Userland/DevTools/UserspaceEmulator/SoftFPU.h
Linus Groh 6e19ab2bbc AK+Everywhere: Rename String to DeprecatedString
We have a new, improved string type coming up in AK (OOM aware, no null
state), and while it's going to use UTF-8, the name UTF8String is a
mouthful - so let's free up the String name by renaming the existing
class.
Making the old one have an annoying name will hopefully also help with
quick adoption :^)
2022-12-06 08:54:33 +01:00

588 lines
17 KiB
C++

/*
* Copyright (c) 2021, Leon Albrecht <leon2002.la@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "Report.h"
#include <AK/Concepts.h>
#include <AK/FPControl.h>
#include <AK/SIMD.h>
#include <LibX86/Instruction.h>
#include <LibX86/Interpreter.h>
#include <math.h>
#include <string.h>
namespace UserspaceEmulator {
using namespace AK::SIMD;
using AK::RoundingMode;
class Emulator;
class SoftCPU;
union MMX {
u64 raw;
c8x8 v8;
i16x4 v16;
i32x2 v32;
u16x4 v16u;
u32x2 v32u;
};
static_assert(AssertSize<MMX, sizeof(u64)>());
class SoftFPU final {
public:
SoftFPU(Emulator& emulator, SoftCPU& cpu)
: m_emulator(emulator)
, m_cpu(cpu)
, m_fpu_cw { 0x037F }
{
}
ALWAYS_INLINE bool c0() const { return m_fpu_c0; }
ALWAYS_INLINE bool c1() const { return m_fpu_c1; }
ALWAYS_INLINE bool c2() const { return m_fpu_c2; }
ALWAYS_INLINE bool c3() const { return m_fpu_c3; }
ALWAYS_INLINE void set_c0(bool val) { m_fpu_c0 = val; }
ALWAYS_INLINE void set_c1(bool val) { m_fpu_c1 = val; }
ALWAYS_INLINE void set_c2(bool val) { m_fpu_c2 = val; }
ALWAYS_INLINE void set_c3(bool val) { m_fpu_c3 = val; }
long double fpu_get(u8 index);
void fpu_push(long double value);
long double fpu_pop();
void fpu_set_absolute(u8 index, long double value);
void fpu_set(u8 index, long double value);
MMX mmx_get(u8 index) const;
void mmx_set(u8 index, MMX value);
private:
friend class SoftCPU;
Emulator& m_emulator;
SoftCPU& m_cpu;
enum class FPU_Exception : u8 {
InvalidOperation,
DenormalizedOperand,
ZeroDivide,
Overflow,
Underflow,
Precision,
StackFault,
};
enum class FPU_Tag : u8 {
Valid = 0b00,
Zero = 0b01,
Special = 0b10,
Empty = 0b11
};
void fpu_dump_env()
{
reportln("Exceptions: #I:{} #D:{} #Z:{} #O:{} #U:{} #P:{} #SF:{} Summary:{}"sv,
m_fpu_error_invalid,
m_fpu_error_denorm,
m_fpu_error_zero_div,
m_fpu_error_overflow,
m_fpu_error_underflow,
m_fpu_error_precision,
m_fpu_error_stackfault,
m_fpu_error_summary);
reportln("Masks: #I:{} #D:{} #Z:{} #O:{} #U:{} #P:{}"sv,
m_fpu_cw.mask_invalid,
m_fpu_cw.mask_denorm,
m_fpu_cw.mask_zero_div,
m_fpu_cw.mask_overflow,
m_fpu_cw.mask_underflow,
m_fpu_cw.mask_precision);
reportln("C0:{} C1:{} C2:{} C3:{}"sv, c0(), c1(), c2(), c3());
reportln("fpu-stacktop: {}"sv, m_fpu_stack_top);
reportln("fpu-stack /w stacktop (real):"sv);
for (u8 i = 0; i < 8; ++i) {
reportln("\t{} ({}): fp {} ({}), mmx {:016x}"sv,
i, (u8)((m_fpu_stack_top + i) % 8),
m_storage[(m_fpu_stack_top + i) % 8].fp, fpu_is_set(i) ? "set" : "free",
m_storage[(m_fpu_stack_top + i) % 8].mmx.raw);
}
}
DeprecatedString fpu_exception_string(FPU_Exception ex)
{
switch (ex) {
case FPU_Exception::StackFault:
return "Stackfault";
case FPU_Exception::InvalidOperation:
return "Invalid Operation";
case FPU_Exception::DenormalizedOperand:
return "Denormalized Operand";
case FPU_Exception::ZeroDivide:
return "Divide by Zero";
case FPU_Exception::Overflow:
return "Overflow";
case FPU_Exception::Underflow:
return "Underflow";
case FPU_Exception::Precision:
return "Precision";
}
VERIFY_NOT_REACHED();
}
// FIXME: Technically we should check for exceptions after each insn, too,
// this might be important for FLDENV, but otherwise it should
// be fine this way
void fpu_set_exception(FPU_Exception ex);
ALWAYS_INLINE void fpu_set_stack_overflow()
{
reportln("Stack Overflow"sv);
set_c1(1);
fpu_set_exception(FPU_Exception::StackFault);
}
ALWAYS_INLINE void fpu_set_stack_underflow()
{
reportln("Stack Underflow"sv);
set_c1(0);
fpu_set_exception(FPU_Exception::StackFault);
}
constexpr FPU_Tag fpu_get_tag_absolute(u8 index) const
{
switch (index) {
case 0:
return FPU_Tag(m_fpu_status_0);
case 1:
return FPU_Tag(m_fpu_status_1);
case 2:
return FPU_Tag(m_fpu_status_2);
case 3:
return FPU_Tag(m_fpu_status_3);
case 4:
return FPU_Tag(m_fpu_status_4);
case 5:
return FPU_Tag(m_fpu_status_5);
case 6:
return FPU_Tag(m_fpu_status_6);
case 7:
return FPU_Tag(m_fpu_status_7);
default:
VERIFY_NOT_REACHED();
}
}
constexpr FPU_Tag fpu_get_tag(u8 index) const
{
VERIFY(index < 8);
return fpu_get_tag_absolute((m_fpu_stack_top + index) % 8);
}
ALWAYS_INLINE void fpu_set_tag_absolute(u8 index, FPU_Tag tag)
{
switch (index) {
case 0:
m_fpu_status_0 = (u8)tag;
break;
case 1:
m_fpu_status_1 = (u8)tag;
break;
case 2:
m_fpu_status_2 = (u8)tag;
break;
case 3:
m_fpu_status_3 = (u8)tag;
break;
case 4:
m_fpu_status_4 = (u8)tag;
break;
case 5:
m_fpu_status_5 = (u8)tag;
break;
case 6:
m_fpu_status_6 = (u8)tag;
break;
case 7:
m_fpu_status_7 = (u8)tag;
break;
default:
VERIFY_NOT_REACHED();
}
}
ALWAYS_INLINE void fpu_set_tag(u8 index, FPU_Tag tag)
{
VERIFY(index < 8);
fpu_set_tag_absolute((m_fpu_stack_top + index) % 8, tag);
}
ALWAYS_INLINE void set_tag_from_value_absolute(u8 index, long double val)
{
switch (fpclassify(val)) {
case FP_ZERO:
fpu_set_tag_absolute(index, FPU_Tag::Zero);
break;
case FP_NAN:
case FP_INFINITE:
case FP_SUBNORMAL:
fpu_set_tag_absolute(index, FPU_Tag::Special);
break;
case FP_NORMAL:
fpu_set_tag_absolute(index, FPU_Tag::Valid);
break;
default:
VERIFY_NOT_REACHED();
}
}
ALWAYS_INLINE void set_tag_from_value(u8 index, long double val)
{
set_tag_from_value_absolute((m_fpu_stack_top + index) % 8, val);
}
ALWAYS_INLINE bool fpu_isnan(u8 index)
{
return isnan(fpu_get(index));
}
ALWAYS_INLINE bool fpu_is_set(u8 index) const
{
return fpu_get_tag_absolute((m_fpu_stack_top + index) % 8) != FPU_Tag::Empty;
}
ALWAYS_INLINE RoundingMode fpu_get_round_mode() const
{
return m_fpu_cw.rounding_control;
}
template<Arithmetic T>
T round_checked(long double);
template<FloatingPoint T>
T convert_checked(long double);
ALWAYS_INLINE void fpu_set_unordered()
{
set_c0(1);
set_c2(1);
set_c3(1);
}
void warn_if_mmx_absolute(u8 index) const;
void warn_if_fpu_absolute(u8 index) const;
void mmx_common() { m_fpu_tw = 0; }
bool m_reg_is_mmx[8] { false };
union {
long double fp;
struct {
MMX mmx;
u16 __high;
};
} m_storage[8];
AK::X87ControlWord m_fpu_cw;
union {
u16 m_fpu_sw { 0 };
struct {
u16 m_fpu_error_invalid : 1; // pre | IE -> #I (#IS, #IA)
u16 m_fpu_error_denorm : 1; // pre | DE -> #D
u16 m_fpu_error_zero_div : 1; // pre | ZE -> #Z
u16 m_fpu_error_overflow : 1; // post| OE -> #O
u16 m_fpu_error_underflow : 1; // post| UE -> #U
u16 m_fpu_error_precision : 1; // post| PE -> #P
u16 m_fpu_error_stackfault : 1; // SF
u16 m_fpu_error_summary : 1;
u16 m_fpu_c0 : 1;
u16 m_fpu_c1 : 1;
u16 m_fpu_c2 : 1;
u16 m_fpu_stack_top : 3;
u16 m_fpu_c3 : 1;
u16 m_fpu_busy : 1;
};
};
union {
u16 m_fpu_tw { 0xFFFF };
struct {
u16 m_fpu_status_0 : 2;
u16 m_fpu_status_1 : 2;
u16 m_fpu_status_2 : 2;
u16 m_fpu_status_3 : 2;
u16 m_fpu_status_4 : 2;
u16 m_fpu_status_5 : 2;
u16 m_fpu_status_6 : 2;
u16 m_fpu_status_7 : 2;
};
};
u32 m_fpu_ip { 0 };
u16 m_fpu_cs { 0 };
u32 m_fpu_dp { 0 };
u16 m_fpu_ds { 0 };
u16 m_fpu_iop { 0 };
// Instructions
// DATA TRANSFER
void FLD_RM32(const X86::Instruction&);
void FLD_RM64(const X86::Instruction&);
void FLD_RM80(const X86::Instruction&);
void FST_RM32(const X86::Instruction&);
void FST_RM64(const X86::Instruction&);
void FSTP_RM32(const X86::Instruction&);
void FSTP_RM64(const X86::Instruction&);
void FSTP_RM80(const X86::Instruction&);
void FILD_RM32(const X86::Instruction&);
void FILD_RM16(const X86::Instruction&);
void FILD_RM64(const X86::Instruction&);
void FIST_RM16(const X86::Instruction&);
void FIST_RM32(const X86::Instruction&);
void FISTP_RM16(const X86::Instruction&);
void FISTP_RM32(const X86::Instruction&);
void FISTP_RM64(const X86::Instruction&);
void FISTTP_RM16(const X86::Instruction&);
void FISTTP_RM32(const X86::Instruction&);
void FISTTP_RM64(const X86::Instruction&);
void FBLD_M80(const X86::Instruction&);
void FBSTP_M80(const X86::Instruction&);
void FXCH(const X86::Instruction&);
void FCMOVE(const X86::Instruction&);
void FCMOVNE(const X86::Instruction&);
void FCMOVB(const X86::Instruction&);
void FCMOVBE(const X86::Instruction&);
void FCMOVNB(const X86::Instruction&);
void FCMOVNBE(const X86::Instruction&);
void FCMOVU(const X86::Instruction&);
void FCMOVNU(const X86::Instruction&);
// BASIC ARITHMETIC
void FADD_RM32(const X86::Instruction&);
void FADD_RM64(const X86::Instruction&);
void FADDP(const X86::Instruction&);
void FIADD_RM16(const X86::Instruction&);
void FIADD_RM32(const X86::Instruction&);
void FSUB_RM32(const X86::Instruction&);
void FSUB_RM64(const X86::Instruction&);
void FSUBP(const X86::Instruction&);
void FSUBR_RM32(const X86::Instruction&);
void FSUBR_RM64(const X86::Instruction&);
void FSUBRP(const X86::Instruction&);
void FISUB_RM16(const X86::Instruction&);
void FISUB_RM32(const X86::Instruction&);
void FISUBR_RM16(const X86::Instruction&);
void FISUBR_RM32(const X86::Instruction&);
void FMUL_RM32(const X86::Instruction&);
void FMUL_RM64(const X86::Instruction&);
void FMULP(const X86::Instruction&);
void FIMUL_RM16(const X86::Instruction&);
void FIMUL_RM32(const X86::Instruction&);
void FDIV_RM32(const X86::Instruction&);
void FDIV_RM64(const X86::Instruction&);
void FDIVP(const X86::Instruction&);
void FDIVR_RM32(const X86::Instruction&);
void FDIVR_RM64(const X86::Instruction&);
void FDIVRP(const X86::Instruction&);
void FIDIV_RM16(const X86::Instruction&);
void FIDIV_RM32(const X86::Instruction&);
void FIDIVR_RM16(const X86::Instruction&);
void FIDIVR_RM32(const X86::Instruction&);
void FPREM(const X86::Instruction&);
void FPREM1(const X86::Instruction&);
void FABS(const X86::Instruction&);
void FCHS(const X86::Instruction&);
void FRNDINT(const X86::Instruction&);
void FSCALE(const X86::Instruction&);
void FSQRT(const X86::Instruction&);
void FXTRACT(const X86::Instruction&);
// COMPARISON
void FCOM_RM32(const X86::Instruction&);
void FCOM_RM64(const X86::Instruction&);
void FCOMP_RM32(const X86::Instruction&);
void FCOMP_RM64(const X86::Instruction&);
void FCOMPP(const X86::Instruction&);
void FCOMI(const X86::Instruction&);
void FCOMIP(const X86::Instruction&);
void FUCOM(const X86::Instruction&);
void FUCOMP(const X86::Instruction&);
void FUCOMPP(const X86::Instruction&);
void FUCOMI(const X86::Instruction&);
void FUCOMIP(const X86::Instruction&);
void FICOM_RM16(const X86::Instruction&);
void FICOM_RM32(const X86::Instruction&);
void FICOMP_RM16(const X86::Instruction&);
void FICOMP_RM32(const X86::Instruction&);
void FTST(const X86::Instruction&);
void FXAM(const X86::Instruction&);
// TRANSCENDENTAL
void FSIN(const X86::Instruction&);
void FCOS(const X86::Instruction&);
void FSINCOS(const X86::Instruction&);
void FPTAN(const X86::Instruction&);
void FPATAN(const X86::Instruction&);
void F2XM1(const X86::Instruction&);
void FYL2X(const X86::Instruction&);
void FYL2XP1(const X86::Instruction&);
// CONSTANT LOAD
void FLD1(const X86::Instruction&);
void FLDZ(const X86::Instruction&);
void FLDPI(const X86::Instruction&);
void FLDL2E(const X86::Instruction&);
void FLDLN2(const X86::Instruction&);
void FLDL2T(const X86::Instruction&);
void FLDLG2(const X86::Instruction&);
// CONTROL
void FINCSTP(const X86::Instruction&);
void FDECSTP(const X86::Instruction&);
void FFREE(const X86::Instruction&);
void FFREEP(const X86::Instruction&); // undocumented
// FIXME: Non N- versions?
void FNINIT(const X86::Instruction&);
void FNCLEX(const X86::Instruction&);
void FNSTCW(const X86::Instruction&);
void FLDCW(const X86::Instruction&);
void FNSTENV(const X86::Instruction&);
void FLDENV(const X86::Instruction&);
void FNSAVE(const X86::Instruction&);
void FRSTOR(const X86::Instruction&);
void FNSTSW(const X86::Instruction&);
void FNSTSW_AX(const X86::Instruction&);
// FIXME: WAIT && FWAIT
void FNOP(const X86::Instruction&);
// FPU & SIMD MANAGEMENT
// FIXME: FXSAVE && FXRSTOR
// DO NOTHING?
// FIXME: FENI, FDISI, FSETPM
void FNENI(const X86::Instruction&);
void FNDISI(const X86::Instruction&);
void FNSETPM(const X86::Instruction&);
// MMX
// ARITHMETIC
void PADDB_mm1_mm2m64(const X86::Instruction&);
void PADDW_mm1_mm2m64(const X86::Instruction&);
void PADDD_mm1_mm2m64(const X86::Instruction&);
void PADDSB_mm1_mm2m64(const X86::Instruction&);
void PADDSW_mm1_mm2m64(const X86::Instruction&);
void PADDUSB_mm1_mm2m64(const X86::Instruction&);
void PADDUSW_mm1_mm2m64(const X86::Instruction&);
void PSUBB_mm1_mm2m64(const X86::Instruction&);
void PSUBW_mm1_mm2m64(const X86::Instruction&);
void PSUBD_mm1_mm2m64(const X86::Instruction&);
void PSUBSB_mm1_mm2m64(const X86::Instruction&);
void PSUBSW_mm1_mm2m64(const X86::Instruction&);
void PSUBUSB_mm1_mm2m64(const X86::Instruction&);
void PSUBUSW_mm1_mm2m64(const X86::Instruction&);
void PMULHW_mm1_mm2m64(const X86::Instruction&);
void PMULLW_mm1_mm2m64(const X86::Instruction&);
void PMADDWD_mm1_mm2m64(const X86::Instruction&);
// COMPARISON
void PCMPEQB_mm1_mm2m64(const X86::Instruction&);
void PCMPEQW_mm1_mm2m64(const X86::Instruction&);
void PCMPEQD_mm1_mm2m64(const X86::Instruction&);
void PCMPGTB_mm1_mm2m64(const X86::Instruction&);
void PCMPGTW_mm1_mm2m64(const X86::Instruction&);
void PCMPGTD_mm1_mm2m64(const X86::Instruction&);
// CONVERSION
void PACKSSDW_mm1_mm2m64(const X86::Instruction&);
void PACKSSWB_mm1_mm2m64(const X86::Instruction&);
void PACKUSWB_mm1_mm2m64(const X86::Instruction&);
// UNPACK
void PUNPCKHBW_mm1_mm2m64(const X86::Instruction&);
void PUNPCKHWD_mm1_mm2m64(const X86::Instruction&);
void PUNPCKHDQ_mm1_mm2m64(const X86::Instruction&);
void PUNPCKLBW_mm1_mm2m32(const X86::Instruction&);
void PUNPCKLWD_mm1_mm2m32(const X86::Instruction&);
void PUNPCKLDQ_mm1_mm2m32(const X86::Instruction&);
// LOGICAL
void PAND_mm1_mm2m64(const X86::Instruction&);
void PANDN_mm1_mm2m64(const X86::Instruction&);
void POR_mm1_mm2m64(const X86::Instruction&);
void PXOR_mm1_mm2m64(const X86::Instruction&);
// SHIFT
void PSLLW_mm1_mm2m64(const X86::Instruction&);
void PSLLW_mm1_imm8(const X86::Instruction&);
void PSLLD_mm1_mm2m64(const X86::Instruction&);
void PSLLD_mm1_imm8(const X86::Instruction&);
void PSLLQ_mm1_mm2m64(const X86::Instruction&);
void PSLLQ_mm1_imm8(const X86::Instruction&);
void PSRAW_mm1_mm2m64(const X86::Instruction&);
void PSRAW_mm1_imm8(const X86::Instruction&);
void PSRAD_mm1_mm2m64(const X86::Instruction&);
void PSRAD_mm1_imm8(const X86::Instruction&);
void PSRLW_mm1_mm2m64(const X86::Instruction&);
void PSRLW_mm1_imm8(const X86::Instruction&);
void PSRLD_mm1_mm2m64(const X86::Instruction&);
void PSRLD_mm1_imm8(const X86::Instruction&);
void PSRLQ_mm1_mm2m64(const X86::Instruction&);
void PSRLQ_mm1_imm8(const X86::Instruction&);
// DATA TRANSFER
void MOVD_mm1_rm32(const X86::Instruction&);
void MOVD_rm32_mm2(const X86::Instruction&);
void MOVQ_mm1_mm2m64(const X86::Instruction&);
void MOVQ_mm1m64_mm2(const X86::Instruction&);
void MOVQ_mm1_rm64(const X86::Instruction&); // long mode
void MOVQ_rm64_mm2(const X86::Instruction&); // long mode
// EMPTY MMX STATE
void EMMS(const X86::Instruction&);
};
}