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
)
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)

View file

@ -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<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
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);
T result = static_cast<T>(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<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
return static_cast<T>(value);
}
template<FloatingPoint T>
ALWAYS_INLINE T SoftFPU::fpu_convert_checked(long double value)
{
T result = fpu_convert<T>(value);
T result = static_cast<T>(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<float>(fpu_get(0));
float f32 = convert_checked<float>(fpu_get(0));
if (fpu_is_set(0))
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()) {
fpu_set(insn.modrm().register_index(), fpu_get(0));
} else {
double f64 = fpu_convert_checked<double>(fpu_get(0));
double f64 = convert_checked<double>(fpu_get(0));
if (fpu_is_set(0))
insn.modrm().write64(m_cpu, insn, shadow_wrap_as_initialized(bit_cast<u64>(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<i16>(f);
auto int16 = round_checked<i16>(f);
// FIXME: Respect shadow values
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());
auto f = fpu_get(0);
set_c1(0);
auto int32 = fpu_round_checked<i32>(f);
auto int32 = round_checked<i32>(f);
// FIXME: Respect shadow values
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());
auto f = fpu_pop();
set_c1(0);
auto i64 = fpu_round_checked<int64_t>(f);
auto i64 = round_checked<int64_t>(f);
// FIXME: Respect shadow values
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&)
{
// 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);
}
@ -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);

View file

@ -8,6 +8,7 @@
#include "Report.h"
#include <AK/Concepts.h>
#include <AK/FPControl.h>
#include <AK/SIMD.h>
#include <LibX86/Instruction.h>
#include <LibX86/Interpreter.h>
@ -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<Arithmetic T>
T fpu_round(long double) const;
template<Arithmetic T>
T fpu_round_checked(long double);
T round_checked(long double);
template<FloatingPoint T>
T fpu_convert(long double) const;
template<FloatingPoint T>
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 };

View file

@ -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<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)
@ -222,7 +243,7 @@ void SoftVPU::CVTSS2SI_r32_xmm2m32(X86::Instruction const& insn)
{
// FIXME: Raise Invalid, Precision
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)

View file

@ -6,12 +6,14 @@
#pragma once
#include <AK/FPControl.h>
#include <AK/SIMD.h>
#include <AK/Types.h>
#include <LibX86/Instruction.h>
#include <math.h>
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&);