UserspaceEmulator: Delegate rounding to the actual hardware

This also makes us a bit more accurate, due to better rounding of
intermediate results.

This also gives us the flush-to-zero and denormals-are-zero SSE settings
for free! (Assuming UE is build with SSE)
This commit is contained in:
Hendiadyoin1 2022-04-08 17:09:19 +02:00 committed by Linus Groh
parent 56a31ab376
commit 6c41267dcf
Notes: sideshowbarker 2024-07-17 11:11:41 +09:00
5 changed files with 91 additions and 126 deletions

View file

@ -20,7 +20,7 @@ set(SOURCES
main.cpp main.cpp
) )
add_compile_options(-mmmx -Wno-psabi) add_compile_options(-mmmx -Wno-psabi -frounding-math)
serenity_bin(UserspaceEmulator) serenity_bin(UserspaceEmulator)
target_link_libraries(UserspaceEmulator LibX86 LibDebug LibCore LibPthread LibLine) target_link_libraries(UserspaceEmulator LibX86 LibDebug LibCore LibPthread LibLine)

View file

@ -123,32 +123,32 @@ ALWAYS_INLINE void SoftFPU::fpu_set_exception(FPU_Exception ex)
break; break;
case FPU_Exception::InvalidOperation: case FPU_Exception::InvalidOperation:
m_fpu_error_invalid = 1; m_fpu_error_invalid = 1;
if (!m_fpu_mask_invalid) if (!m_fpu_cw.mask_invalid)
break; break;
return; return;
case FPU_Exception::DenormalizedOperand: case FPU_Exception::DenormalizedOperand:
m_fpu_error_denorm = 1; m_fpu_error_denorm = 1;
if (!m_fpu_mask_denorm) if (!m_fpu_cw.mask_denorm)
break; break;
return; return;
case FPU_Exception::ZeroDivide: case FPU_Exception::ZeroDivide:
m_fpu_error_zero_div = 1; m_fpu_error_zero_div = 1;
if (!m_fpu_mask_zero_div) if (!m_fpu_cw.mask_zero_div)
break; break;
return; return;
case FPU_Exception::Overflow: case FPU_Exception::Overflow:
m_fpu_error_overflow = 1; m_fpu_error_overflow = 1;
if (!m_fpu_mask_overflow) if (!m_fpu_cw.mask_overflow)
break; break;
return; return;
case FPU_Exception::Underflow: case FPU_Exception::Underflow:
m_fpu_error_underflow = 1; m_fpu_error_underflow = 1;
if (!m_fpu_mask_underflow) if (!m_fpu_cw.mask_underflow)
break; break;
return; return;
case FPU_Exception::Precision: case FPU_Exception::Precision:
m_fpu_error_precision = 1; m_fpu_error_precision = 1;
if (!m_fpu_mask_precision) if (!m_fpu_cw.mask_precision)
break; break;
return; return;
} }
@ -168,27 +168,9 @@ ALWAYS_INLINE void SoftFPU::fpu_set_exception(FPU_Exception ex)
} }
template<Arithmetic T> template<Arithmetic T>
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 T result = static_cast<T>(rintl(value));
switch (fpu_get_round_mode()) {
case RoundingMode::NEAREST:
return static_cast<T>(roundl(value));
case RoundingMode::DOWN:
return static_cast<T>(floorl(value));
case RoundingMode::UP:
return static_cast<T>(ceill(value));
case RoundingMode::TRUNC:
return static_cast<T>(truncl(value));
default:
VERIFY_NOT_REACHED();
}
}
template<Arithmetic T>
ALWAYS_INLINE T SoftFPU::fpu_round_checked(long double value)
{
T result = fpu_round<T>(value);
if (result != value) if (result != value)
fpu_set_exception(FPU_Exception::Precision); fpu_set_exception(FPU_Exception::Precision);
if (result > value) if (result > value)
@ -199,15 +181,9 @@ ALWAYS_INLINE T SoftFPU::fpu_round_checked(long double value)
} }
template<FloatingPoint T> template<FloatingPoint T>
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 T result = static_cast<T>(value);
return static_cast<T>(value);
}
template<FloatingPoint T>
ALWAYS_INLINE T SoftFPU::fpu_convert_checked(long double value)
{
T result = fpu_convert<T>(value);
if (auto rnd = value - result) { if (auto rnd = value - result) {
if (rnd > 0) if (rnd > 0)
set_c1(1); set_c1(1);
@ -254,7 +230,7 @@ void SoftFPU::FLD_RM80(const X86::Instruction& insn)
void SoftFPU::FST_RM32(const X86::Instruction& insn) void SoftFPU::FST_RM32(const X86::Instruction& insn)
{ {
VERIFY(!insn.modrm().is_register()); VERIFY(!insn.modrm().is_register());
float f32 = fpu_convert_checked<float>(fpu_get(0)); float f32 = convert_checked<float>(fpu_get(0));
if (fpu_is_set(0)) if (fpu_is_set(0))
insn.modrm().write32(m_cpu, insn, shadow_wrap_as_initialized(bit_cast<u32>(f32))); insn.modrm().write32(m_cpu, insn, shadow_wrap_as_initialized(bit_cast<u32>(f32)));
@ -266,7 +242,7 @@ void SoftFPU::FST_RM64(const X86::Instruction& insn)
if (insn.modrm().is_register()) { if (insn.modrm().is_register()) {
fpu_set(insn.modrm().register_index(), fpu_get(0)); fpu_set(insn.modrm().register_index(), fpu_get(0));
} else { } else {
double f64 = fpu_convert_checked<double>(fpu_get(0)); double f64 = convert_checked<double>(fpu_get(0));
if (fpu_is_set(0)) if (fpu_is_set(0))
insn.modrm().write64(m_cpu, insn, shadow_wrap_as_initialized(bit_cast<u64>(f64))); insn.modrm().write64(m_cpu, insn, shadow_wrap_as_initialized(bit_cast<u64>(f64)));
else else
@ -335,7 +311,7 @@ void SoftFPU::FIST_RM16(const X86::Instruction& insn)
VERIFY(!insn.modrm().is_register()); VERIFY(!insn.modrm().is_register());
auto f = fpu_get(0); auto f = fpu_get(0);
set_c1(0); set_c1(0);
auto int16 = fpu_round_checked<i16>(f); auto int16 = round_checked<i16>(f);
// FIXME: Respect shadow values // FIXME: Respect shadow values
insn.modrm().write16(m_cpu, insn, shadow_wrap_as_initialized(bit_cast<u16>(int16))); insn.modrm().write16(m_cpu, insn, shadow_wrap_as_initialized(bit_cast<u16>(int16)));
@ -345,7 +321,7 @@ void SoftFPU::FIST_RM32(const X86::Instruction& insn)
VERIFY(!insn.modrm().is_register()); VERIFY(!insn.modrm().is_register());
auto f = fpu_get(0); auto f = fpu_get(0);
set_c1(0); set_c1(0);
auto int32 = fpu_round_checked<i32>(f); auto int32 = round_checked<i32>(f);
// FIXME: Respect shadow values // FIXME: Respect shadow values
insn.modrm().write32(m_cpu, insn, shadow_wrap_as_initialized(bit_cast<u32>(int32))); insn.modrm().write32(m_cpu, insn, shadow_wrap_as_initialized(bit_cast<u32>(int32)));
} }
@ -365,7 +341,7 @@ void SoftFPU::FISTP_RM64(const X86::Instruction& insn)
VERIFY(!insn.modrm().is_register()); VERIFY(!insn.modrm().is_register());
auto f = fpu_pop(); auto f = fpu_pop();
set_c1(0); set_c1(0);
auto i64 = fpu_round_checked<int64_t>(f); auto i64 = round_checked<int64_t>(f);
// FIXME: Respect shadow values // FIXME: Respect shadow values
insn.modrm().write64(m_cpu, insn, shadow_wrap_as_initialized(bit_cast<u64>(i64))); insn.modrm().write64(m_cpu, insn, shadow_wrap_as_initialized(bit_cast<u64>(i64)));
} }
@ -787,7 +763,7 @@ void SoftFPU::FCHS(const X86::Instruction&)
void SoftFPU::FRNDINT(const X86::Instruction&) void SoftFPU::FRNDINT(const X86::Instruction&)
{ {
// FIXME: Raise #IA #D // FIXME: Raise #IA #D
auto res = fpu_round_checked<long double>(fpu_get(0)); auto res = round_checked<long double>(fpu_get(0));
fpu_set(0, res); fpu_set(0, res);
} }
@ -818,7 +794,7 @@ void SoftFPU::FCOMPP(const X86::Instruction&)
{ {
if (fpu_isnan(0) || fpu_isnan(1)) { if (fpu_isnan(0) || fpu_isnan(1)) {
fpu_set_exception(FPU_Exception::InvalidOperation); fpu_set_exception(FPU_Exception::InvalidOperation);
if (m_fpu_mask_invalid) if (m_fpu_cw.mask_invalid)
fpu_set_unordered(); fpu_set_unordered();
} else { } else {
set_c2(0); set_c2(0);
@ -1146,7 +1122,7 @@ void SoftFPU::FFREEP(const X86::Instruction& insn)
void SoftFPU::FNINIT(const X86::Instruction&) void SoftFPU::FNINIT(const X86::Instruction&)
{ {
m_fpu_cw = 0x037F; m_fpu_cw.cw = 0x037F;
m_fpu_sw = 0; m_fpu_sw = 0;
m_fpu_tw = 0xFFFF; m_fpu_tw = 0xFFFF;
@ -1172,11 +1148,23 @@ void SoftFPU::FNCLEX(const X86::Instruction&)
void SoftFPU::FNSTCW(const X86::Instruction& insn) 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) 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) 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); 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); address.set_offset(address.offset() + 4);
m_cpu.write_memory16(address, shadow_wrap_as_initialized(m_fpu_sw)); m_cpu.write_memory16(address, shadow_wrap_as_initialized(m_fpu_sw));
address.set_offset(address.offset() + 4); 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); auto address = insn.modrm().resolve(m_cpu, insn);
// FIXME: Shadow Values // 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); address.set_offset(address.offset() + 4);
m_fpu_sw = m_cpu.read_memory16(address).value(); m_fpu_sw = m_cpu.read_memory16(address).value();
address.set_offset(address.offset() + 4); address.set_offset(address.offset() + 4);

View file

@ -8,6 +8,7 @@
#include "Report.h" #include "Report.h"
#include <AK/Concepts.h> #include <AK/Concepts.h>
#include <AK/FPControl.h>
#include <AK/SIMD.h> #include <AK/SIMD.h>
#include <LibX86/Instruction.h> #include <LibX86/Instruction.h>
#include <LibX86/Interpreter.h> #include <LibX86/Interpreter.h>
@ -17,6 +18,8 @@
namespace UserspaceEmulator { namespace UserspaceEmulator {
using namespace AK::SIMD; using namespace AK::SIMD;
using AK::RoundingMode;
class Emulator; class Emulator;
class SoftCPU; class SoftCPU;
@ -35,6 +38,7 @@ public:
SoftFPU(Emulator& emulator, SoftCPU& cpu) SoftFPU(Emulator& emulator, SoftCPU& cpu)
: m_emulator(emulator) : m_emulator(emulator)
, m_cpu(cpu) , m_cpu(cpu)
, m_fpu_cw { 0x037F }
{ {
} }
@ -81,13 +85,6 @@ private:
Empty = 0b11 Empty = 0b11
}; };
enum class RoundingMode : u8 {
NEAREST = 0b00,
DOWN = 0b01,
UP = 0b10,
TRUNC = 0b11
};
void fpu_dump_env() void fpu_dump_env()
{ {
reportln("Exceptions: #I:{} #D:{} #Z:{} #O:{} #U:{} #P:{} #SF:{} Summary:{}", reportln("Exceptions: #I:{} #D:{} #Z:{} #O:{} #U:{} #P:{} #SF:{} Summary:{}",
@ -100,12 +97,12 @@ private:
m_fpu_error_stackfault, m_fpu_error_stackfault,
m_fpu_error_summary); m_fpu_error_summary);
reportln("Masks: #I:{} #D:{} #Z:{} #O:{} #U:{} #P:{}", reportln("Masks: #I:{} #D:{} #Z:{} #O:{} #U:{} #P:{}",
m_fpu_mask_invalid, m_fpu_cw.mask_invalid,
m_fpu_mask_denorm, m_fpu_cw.mask_denorm,
m_fpu_mask_zero_div, m_fpu_cw.mask_zero_div,
m_fpu_mask_overflow, m_fpu_cw.mask_overflow,
m_fpu_mask_underflow, m_fpu_cw.mask_underflow,
m_fpu_mask_precision); m_fpu_cw.mask_precision);
reportln("C0:{} C1:{} C2:{} C3:{}", c0(), c1(), c2(), c3()); reportln("C0:{} C1:{} C2:{} C3:{}", c0(), c1(), c2(), c3());
reportln("fpu-stacktop: {}", m_fpu_stack_top); reportln("fpu-stacktop: {}", m_fpu_stack_top);
reportln("fpu-stack /w stacktop (real):"); reportln("fpu-stack /w stacktop (real):");
@ -261,18 +258,14 @@ private:
ALWAYS_INLINE RoundingMode fpu_get_round_mode() const ALWAYS_INLINE RoundingMode fpu_get_round_mode() const
{ {
return RoundingMode(m_fpu_round_mode); return m_fpu_cw.rounding_control;
} }
template<Arithmetic T> template<Arithmetic T>
T fpu_round(long double) const; T round_checked(long double);
template<Arithmetic T>
T fpu_round_checked(long double);
template<FloatingPoint T> template<FloatingPoint T>
T fpu_convert(long double) const; T convert_checked(long double);
template<FloatingPoint T>
T fpu_convert_checked(long double);
ALWAYS_INLINE void fpu_set_unordered() ALWAYS_INLINE void fpu_set_unordered()
{ {
@ -295,22 +288,7 @@ private:
}; };
} m_storage[8]; } m_storage[8];
union { AK::X87ControlWord m_fpu_cw;
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
};
};
union { union {
u16 m_fpu_sw { 0 }; u16 m_fpu_sw { 0 };

View file

@ -18,14 +18,35 @@ void SoftVPU::PREFETCHT2(X86::Instruction const&) { TODO(); }
void SoftVPU::LDMXCSR(X86::Instruction const& insn) void SoftVPU::LDMXCSR(X86::Instruction const& insn)
{ {
// FIXME: Shadows // 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 // #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) void SoftVPU::STMXCSR(X86::Instruction const& insn)
{ {
// FIXME: Shadows // FIXME: Shadows
insn.modrm().write32(m_cpu, insn, ValueWithShadow<u32>::create_initialized(m_mxcsr)); insn.modrm().write32(m_cpu, insn, ValueWithShadow<u32>::create_initialized(m_mxcsr.mxcsr));
} }
void SoftVPU::MOVUPS_xmm1_xmm2m128(X86::Instruction const& insn) 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 // FIXME: Raise Invalid, Precision
insn.modrm().write32(m_cpu, insn, insn.modrm().write32(m_cpu, insn,
ValueWithShadow<u32>::create_initialized((u32)lround(m_xmm[insn.modrm().reg()].ps[0]))); ValueWithShadow<u32>::create_initialized(static_cast<i32>(m_xmm[insn.modrm().reg()].ps[0])));
} }
void SoftVPU::UCOMISS_xmm1_xmm2m32(X86::Instruction const& insn) void SoftVPU::UCOMISS_xmm1_xmm2m32(X86::Instruction const& insn)

View file

@ -6,12 +6,14 @@
#pragma once #pragma once
#include <AK/FPControl.h>
#include <AK/SIMD.h> #include <AK/SIMD.h>
#include <AK/Types.h> #include <AK/Types.h>
#include <LibX86/Instruction.h> #include <LibX86/Instruction.h>
#include <math.h> #include <math.h>
namespace UserspaceEmulator { namespace UserspaceEmulator {
using AK::RoundingMode;
using namespace AK::SIMD; using namespace AK::SIMD;
class Emulator; class Emulator;
class SoftCPU; class SoftCPU;
@ -57,51 +59,17 @@ public:
// FIXME: More with VEX prefix // 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: private:
friend SoftCPU; friend SoftCPU;
Emulator& m_emulator; Emulator& m_emulator;
SoftCPU& m_cpu; SoftCPU& m_cpu;
XMM m_xmm[8]; XMM m_xmm[8];
union {
u32 m_mxcsr; // FIXME: Maybe unimplemented features:
struct { // * DAZ
u32 invalid_operation_flag : 1; // IE // * FTZ
u32 denormal_operation_flag : 1; // DE AK::MXCSR m_mxcsr;
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;
};
};
void PREFETCHTNTA(X86::Instruction const&); void PREFETCHTNTA(X86::Instruction const&);
void PREFETCHT0(X86::Instruction const&); void PREFETCHT0(X86::Instruction const&);