diff --git a/Userland/DevTools/UserspaceEmulator/CMakeLists.txt b/Userland/DevTools/UserspaceEmulator/CMakeLists.txt index acb608dd32..3f7ab6387b 100644 --- a/Userland/DevTools/UserspaceEmulator/CMakeLists.txt +++ b/Userland/DevTools/UserspaceEmulator/CMakeLists.txt @@ -20,7 +20,7 @@ set(SOURCES main.cpp ) -add_compile_options(-mmmx -Wno-psabi) +add_compile_options(-mmmx -Wno-psabi -frounding-math) serenity_bin(UserspaceEmulator) target_link_libraries(UserspaceEmulator LibX86 LibDebug LibCore LibPthread LibLine) diff --git a/Userland/DevTools/UserspaceEmulator/SoftFPU.cpp b/Userland/DevTools/UserspaceEmulator/SoftFPU.cpp index fe51a14364..efe02f9d4a 100644 --- a/Userland/DevTools/UserspaceEmulator/SoftFPU.cpp +++ b/Userland/DevTools/UserspaceEmulator/SoftFPU.cpp @@ -123,32 +123,32 @@ ALWAYS_INLINE void SoftFPU::fpu_set_exception(FPU_Exception ex) break; case FPU_Exception::InvalidOperation: m_fpu_error_invalid = 1; - if (!m_fpu_mask_invalid) + if (!m_fpu_cw.mask_invalid) break; return; case FPU_Exception::DenormalizedOperand: m_fpu_error_denorm = 1; - if (!m_fpu_mask_denorm) + if (!m_fpu_cw.mask_denorm) break; return; case FPU_Exception::ZeroDivide: m_fpu_error_zero_div = 1; - if (!m_fpu_mask_zero_div) + if (!m_fpu_cw.mask_zero_div) break; return; case FPU_Exception::Overflow: m_fpu_error_overflow = 1; - if (!m_fpu_mask_overflow) + if (!m_fpu_cw.mask_overflow) break; return; case FPU_Exception::Underflow: m_fpu_error_underflow = 1; - if (!m_fpu_mask_underflow) + if (!m_fpu_cw.mask_underflow) break; return; case FPU_Exception::Precision: m_fpu_error_precision = 1; - if (!m_fpu_mask_precision) + if (!m_fpu_cw.mask_precision) break; return; } @@ -168,27 +168,9 @@ ALWAYS_INLINE void SoftFPU::fpu_set_exception(FPU_Exception ex) } template -ALWAYS_INLINE T SoftFPU::fpu_round(long double value) const +ALWAYS_INLINE T SoftFPU::round_checked(long double value) { - // FIXME: may need to set indefinite values manually - switch (fpu_get_round_mode()) { - case RoundingMode::NEAREST: - return static_cast(roundl(value)); - case RoundingMode::DOWN: - return static_cast(floorl(value)); - case RoundingMode::UP: - return static_cast(ceill(value)); - case RoundingMode::TRUNC: - return static_cast(truncl(value)); - default: - VERIFY_NOT_REACHED(); - } -} - -template -ALWAYS_INLINE T SoftFPU::fpu_round_checked(long double value) -{ - T result = fpu_round(value); + T result = static_cast(rintl(value)); if (result != value) fpu_set_exception(FPU_Exception::Precision); if (result > value) @@ -199,15 +181,9 @@ ALWAYS_INLINE T SoftFPU::fpu_round_checked(long double value) } template -ALWAYS_INLINE T SoftFPU::fpu_convert(long double value) const +ALWAYS_INLINE T SoftFPU::convert_checked(long double value) { - // FIXME: actually round the right way - return static_cast(value); -} -template -ALWAYS_INLINE T SoftFPU::fpu_convert_checked(long double value) -{ - T result = fpu_convert(value); + T result = static_cast(value); if (auto rnd = value - result) { if (rnd > 0) set_c1(1); @@ -254,7 +230,7 @@ void SoftFPU::FLD_RM80(const X86::Instruction& insn) void SoftFPU::FST_RM32(const X86::Instruction& insn) { VERIFY(!insn.modrm().is_register()); - float f32 = fpu_convert_checked(fpu_get(0)); + float f32 = convert_checked(fpu_get(0)); if (fpu_is_set(0)) insn.modrm().write32(m_cpu, insn, shadow_wrap_as_initialized(bit_cast(f32))); @@ -266,7 +242,7 @@ void SoftFPU::FST_RM64(const X86::Instruction& insn) if (insn.modrm().is_register()) { fpu_set(insn.modrm().register_index(), fpu_get(0)); } else { - double f64 = fpu_convert_checked(fpu_get(0)); + double f64 = convert_checked(fpu_get(0)); if (fpu_is_set(0)) insn.modrm().write64(m_cpu, insn, shadow_wrap_as_initialized(bit_cast(f64))); else @@ -335,7 +311,7 @@ void SoftFPU::FIST_RM16(const X86::Instruction& insn) VERIFY(!insn.modrm().is_register()); auto f = fpu_get(0); set_c1(0); - auto int16 = fpu_round_checked(f); + auto int16 = round_checked(f); // FIXME: Respect shadow values insn.modrm().write16(m_cpu, insn, shadow_wrap_as_initialized(bit_cast(int16))); @@ -345,7 +321,7 @@ void SoftFPU::FIST_RM32(const X86::Instruction& insn) VERIFY(!insn.modrm().is_register()); auto f = fpu_get(0); set_c1(0); - auto int32 = fpu_round_checked(f); + auto int32 = round_checked(f); // FIXME: Respect shadow values insn.modrm().write32(m_cpu, insn, shadow_wrap_as_initialized(bit_cast(int32))); } @@ -365,7 +341,7 @@ void SoftFPU::FISTP_RM64(const X86::Instruction& insn) VERIFY(!insn.modrm().is_register()); auto f = fpu_pop(); set_c1(0); - auto i64 = fpu_round_checked(f); + auto i64 = round_checked(f); // FIXME: Respect shadow values insn.modrm().write64(m_cpu, insn, shadow_wrap_as_initialized(bit_cast(i64))); } @@ -787,7 +763,7 @@ void SoftFPU::FCHS(const X86::Instruction&) void SoftFPU::FRNDINT(const X86::Instruction&) { // FIXME: Raise #IA #D - auto res = fpu_round_checked(fpu_get(0)); + auto res = round_checked(fpu_get(0)); fpu_set(0, res); } @@ -818,7 +794,7 @@ void SoftFPU::FCOMPP(const X86::Instruction&) { if (fpu_isnan(0) || fpu_isnan(1)) { fpu_set_exception(FPU_Exception::InvalidOperation); - if (m_fpu_mask_invalid) + if (m_fpu_cw.mask_invalid) fpu_set_unordered(); } else { set_c2(0); @@ -1146,7 +1122,7 @@ void SoftFPU::FFREEP(const X86::Instruction& insn) void SoftFPU::FNINIT(const X86::Instruction&) { - m_fpu_cw = 0x037F; + m_fpu_cw.cw = 0x037F; m_fpu_sw = 0; m_fpu_tw = 0xFFFF; @@ -1172,11 +1148,23 @@ void SoftFPU::FNCLEX(const X86::Instruction&) void SoftFPU::FNSTCW(const X86::Instruction& insn) { - insn.modrm().write16(m_cpu, insn, shadow_wrap_as_initialized(m_fpu_cw)); + insn.modrm().write16(m_cpu, insn, shadow_wrap_as_initialized(m_fpu_cw.cw)); } void SoftFPU::FLDCW(const X86::Instruction& insn) { - m_fpu_cw = insn.modrm().read16(m_cpu, insn).value(); + m_fpu_cw.cw = insn.modrm().read16(m_cpu, insn).value(); + + // Just let the host's x87 handle the rounding for us + // We do not want to accedentally raise an FP-Exception on the host, so we + // mask all exceptions + AK::X87ControlWord temp = m_fpu_cw; + temp.mask_invalid = 1; + temp.mask_denorm = 1; + temp.mask_zero_div = 1; + temp.mask_overflow = 1; + temp.mask_underflow = 1; + temp.mask_precision = 1; + AK::set_cw_x87(temp); } void SoftFPU::FNSTENV(const X86::Instruction& insn) @@ -1204,7 +1192,7 @@ void SoftFPU::FNSTENV(const X86::Instruction& insn) auto address = insn.modrm().resolve(m_cpu, insn); - m_cpu.write_memory16(address, shadow_wrap_as_initialized(m_fpu_cw)); + m_cpu.write_memory16(address, shadow_wrap_as_initialized(m_fpu_cw.cw)); address.set_offset(address.offset() + 4); m_cpu.write_memory16(address, shadow_wrap_as_initialized(m_fpu_sw)); address.set_offset(address.offset() + 4); @@ -1227,7 +1215,17 @@ void SoftFPU::FLDENV(const X86::Instruction& insn) auto address = insn.modrm().resolve(m_cpu, insn); // FIXME: Shadow Values - m_fpu_cw = m_cpu.read_memory16(address).value(); + m_fpu_cw.cw = m_cpu.read_memory16(address).value(); + // See note in FLDCW + AK::X87ControlWord temp = m_fpu_cw; + temp.mask_invalid = 1; + temp.mask_denorm = 1; + temp.mask_zero_div = 1; + temp.mask_overflow = 1; + temp.mask_underflow = 1; + temp.mask_precision = 1; + AK::set_cw_x87(temp); + address.set_offset(address.offset() + 4); m_fpu_sw = m_cpu.read_memory16(address).value(); address.set_offset(address.offset() + 4); diff --git a/Userland/DevTools/UserspaceEmulator/SoftFPU.h b/Userland/DevTools/UserspaceEmulator/SoftFPU.h index acc2109802..5e142becfb 100644 --- a/Userland/DevTools/UserspaceEmulator/SoftFPU.h +++ b/Userland/DevTools/UserspaceEmulator/SoftFPU.h @@ -8,6 +8,7 @@ #include "Report.h" #include +#include #include #include #include @@ -17,6 +18,8 @@ namespace UserspaceEmulator { using namespace AK::SIMD; +using AK::RoundingMode; + class Emulator; class SoftCPU; @@ -35,6 +38,7 @@ public: SoftFPU(Emulator& emulator, SoftCPU& cpu) : m_emulator(emulator) , m_cpu(cpu) + , m_fpu_cw { 0x037F } { } @@ -81,13 +85,6 @@ private: Empty = 0b11 }; - enum class RoundingMode : u8 { - NEAREST = 0b00, - DOWN = 0b01, - UP = 0b10, - TRUNC = 0b11 - }; - void fpu_dump_env() { reportln("Exceptions: #I:{} #D:{} #Z:{} #O:{} #U:{} #P:{} #SF:{} Summary:{}", @@ -100,12 +97,12 @@ private: m_fpu_error_stackfault, m_fpu_error_summary); reportln("Masks: #I:{} #D:{} #Z:{} #O:{} #U:{} #P:{}", - m_fpu_mask_invalid, - m_fpu_mask_denorm, - m_fpu_mask_zero_div, - m_fpu_mask_overflow, - m_fpu_mask_underflow, - m_fpu_mask_precision); + 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:{}", c0(), c1(), c2(), c3()); reportln("fpu-stacktop: {}", m_fpu_stack_top); reportln("fpu-stack /w stacktop (real):"); @@ -261,18 +258,14 @@ private: ALWAYS_INLINE RoundingMode fpu_get_round_mode() const { - return RoundingMode(m_fpu_round_mode); + return m_fpu_cw.rounding_control; } template - T fpu_round(long double) const; - template - T fpu_round_checked(long double); + T round_checked(long double); template - T fpu_convert(long double) const; - template - T fpu_convert_checked(long double); + T convert_checked(long double); ALWAYS_INLINE void fpu_set_unordered() { @@ -295,22 +288,7 @@ private: }; } m_storage[8]; - union { - u16 m_fpu_cw { 0x037F }; - struct { - u16 m_fpu_mask_invalid : 1; - u16 m_fpu_mask_denorm : 1; - u16 m_fpu_mask_zero_div : 1; - u16 m_fpu_mask_overflow : 1; - u16 m_fpu_mask_underflow : 1; - u16 m_fpu_mask_precision : 1; - u16 : 2; // unused - u16 m_fpu_precission : 2; - u16 m_fpu_round_mode : 2; - u16 m_fpu_infinity_control : 1; - u16 : 3; // unused - }; - }; + AK::X87ControlWord m_fpu_cw; union { u16 m_fpu_sw { 0 }; diff --git a/Userland/DevTools/UserspaceEmulator/SoftVPU.cpp b/Userland/DevTools/UserspaceEmulator/SoftVPU.cpp index 70e9093f9d..541207ecaa 100644 --- a/Userland/DevTools/UserspaceEmulator/SoftVPU.cpp +++ b/Userland/DevTools/UserspaceEmulator/SoftVPU.cpp @@ -18,14 +18,35 @@ void SoftVPU::PREFETCHT2(X86::Instruction const&) { TODO(); } void SoftVPU::LDMXCSR(X86::Instruction const& insn) { // FIXME: Shadows - m_mxcsr = insn.modrm().read32(m_cpu, insn).value(); + m_mxcsr.mxcsr = insn.modrm().read32(m_cpu, insn).value(); + // #GP - General Protection Fault - VERIFY((m_mxcsr & 0xFFFF'0000) == 0); + VERIFY((m_mxcsr.mxcsr & 0xFFFF'0000) == 0); + + // Just let the host's SSE (or if not available x87) handle the rounding for us + // We do not want to accedentally raise an FP-Exception on the host, so we + // mask all exceptions +#ifdef __SSE__ + AK::MXCSR temp = m_mxcsr; + temp.invalid_operation_mask = 1; + temp.denormal_operation_mask = 1; + temp.divide_by_zero_mask = 1; + temp.overflow_mask = 1; + temp.underflow_mask = 1; + temp.precision_mask = 1; + AK::set_mxcsr(temp); +#else + // FIXME: This will mess with x87-land, because it uses the same trick, and + // Does not know of us doing this + AK::X87ControlWord cw { 0x037F }; + cw.rounding_control = m_mxcsr.rounding_control; + AK::set_cw_x87(cw); +#endif } void SoftVPU::STMXCSR(X86::Instruction const& insn) { // FIXME: Shadows - insn.modrm().write32(m_cpu, insn, ValueWithShadow::create_initialized(m_mxcsr)); + insn.modrm().write32(m_cpu, insn, ValueWithShadow::create_initialized(m_mxcsr.mxcsr)); } void SoftVPU::MOVUPS_xmm1_xmm2m128(X86::Instruction const& insn) @@ -222,7 +243,7 @@ void SoftVPU::CVTSS2SI_r32_xmm2m32(X86::Instruction const& insn) { // FIXME: Raise Invalid, Precision insn.modrm().write32(m_cpu, insn, - ValueWithShadow::create_initialized((u32)lround(m_xmm[insn.modrm().reg()].ps[0]))); + ValueWithShadow::create_initialized(static_cast(m_xmm[insn.modrm().reg()].ps[0]))); } void SoftVPU::UCOMISS_xmm1_xmm2m32(X86::Instruction const& insn) diff --git a/Userland/DevTools/UserspaceEmulator/SoftVPU.h b/Userland/DevTools/UserspaceEmulator/SoftVPU.h index a0dbf18d71..2b7614774e 100644 --- a/Userland/DevTools/UserspaceEmulator/SoftVPU.h +++ b/Userland/DevTools/UserspaceEmulator/SoftVPU.h @@ -6,12 +6,14 @@ #pragma once +#include #include #include #include #include namespace UserspaceEmulator { +using AK::RoundingMode; using namespace AK::SIMD; class Emulator; class SoftCPU; @@ -57,51 +59,17 @@ public: // FIXME: More with VEX prefix }; - i32 lround(float value) const - { - // FIXME: This is not yet 100% correct - using enum RoundingMode; - switch ((RoundingMode)rounding_control) { - case NEAREST: - return ::lroundf(value); - case DOWN: - return floorf(value); - case UP: - return ceilf(value); - case TRUNC: - return truncf(value); - default: - VERIFY_NOT_REACHED(); - } - } - private: friend SoftCPU; Emulator& m_emulator; SoftCPU& m_cpu; XMM m_xmm[8]; - union { - u32 m_mxcsr; - struct { - u32 invalid_operation_flag : 1; // IE - u32 denormal_operation_flag : 1; // DE - u32 divide_by_zero_flag : 1; // ZE - u32 overflow_flag : 1; // OE - u32 underflow_flag : 1; // UE - u32 precision_flag : 1; // PE - u32 denormals_are_zero : 1; // FIXME: DAZ - u32 invalid_operation_mask : 1; // IM - u32 denormal_operation_mask : 1; // DM - u32 devide_by_zero_mask : 1; // ZM - u32 overflow_mask : 1; // OM - u32 underflow_mask : 1; // UM - u32 precision_mask : 1; // PM - u32 rounding_control : 2; // FIXME: RC - u32 flush_to_zero : 1; // FIXME: FTZ - u32 __reserved : 16; - }; - }; + + // FIXME: Maybe unimplemented features: + // * DAZ + // * FTZ + AK::MXCSR m_mxcsr; void PREFETCHTNTA(X86::Instruction const&); void PREFETCHT0(X86::Instruction const&);