diff --git a/src/xenia/cpu/backend/x64/x64_sequences.cc b/src/xenia/cpu/backend/x64/x64_sequences.cc index 2891bf50f..c44c44452 100644 --- a/src/xenia/cpu/backend/x64/x64_sequences.cc +++ b/src/xenia/cpu/backend/x64/x64_sequences.cc @@ -1323,7 +1323,11 @@ struct ADD_I64 : Sequence> { }; struct ADD_F32 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { - assert_impossible_sequence(ADD_F32); + e.ChangeMxcsrMode(MXCSRMode::Fpu); + + Xmm src1 = GetInputRegOrConstant(e, i.src1, e.xmm0); + Xmm src2 = GetInputRegOrConstant(e, i.src2, e.xmm1); + e.vaddss(i.dest, src1, src2); } }; struct ADD_F64 : Sequence> { @@ -1441,7 +1445,11 @@ struct SUB_I64 : Sequence> { }; struct SUB_F32 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { - assert_impossible_sequence(SUB_F32); + assert_true(!i.instr->flags); + e.ChangeMxcsrMode(MXCSRMode::Fpu); + Xmm src1 = GetInputRegOrConstant(e, i.src1, e.xmm0); + Xmm src2 = GetInputRegOrConstant(e, i.src2, e.xmm1); + e.vsubss(i.dest, src1, src2); } }; struct SUB_F64 : Sequence> { @@ -1582,7 +1590,12 @@ struct MUL_I64 : Sequence> { }; struct MUL_F32 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { - assert_impossible_sequence(MUL_F32); + assert_true(!i.instr->flags); + e.ChangeMxcsrMode(MXCSRMode::Fpu); + + Xmm src1 = GetInputRegOrConstant(e, i.src1, e.xmm0); + Xmm src2 = GetInputRegOrConstant(e, i.src2, e.xmm1); + e.vmulss(i.dest, src1, src2); } }; struct MUL_F64 : Sequence> { @@ -1861,7 +1874,12 @@ struct DIV_I64 : Sequence> { }; struct DIV_F32 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { - assert_impossible_sequence(DIV_F32); + assert_true(!i.instr->flags); + e.ChangeMxcsrMode(MXCSRMode::Fpu); + + Xmm src1 = GetInputRegOrConstant(e, i.src1, e.xmm0); + Xmm src2 = GetInputRegOrConstant(e, i.src2, e.xmm1); + e.vdivss(i.dest, src1, src2); } }; struct DIV_F64 : Sequence> { diff --git a/src/xenia/cpu/testing/add_test.cc b/src/xenia/cpu/testing/add_test.cc index 8b0a9db54..7248e97ad 100644 --- a/src/xenia/cpu/testing/add_test.cc +++ b/src/xenia/cpu/testing/add_test.cc @@ -305,70 +305,6 @@ TEST_CASE("ADD_I64", "[instr]") { }); } -TEST_CASE("ADD_F32", "[instr]") { - TestFunction test([](HIRBuilder& b) { - StoreFPR(b, 3, - b.Convert(b.Add(b.Convert(LoadFPR(b, 4), FLOAT32_TYPE), - b.Convert(LoadFPR(b, 5), FLOAT32_TYPE)), - FLOAT64_TYPE)); - b.Return(); - }); - test.Run( - [](PPCContext* ctx) { - ctx->f[4] = 0.0; - ctx->f[5] = 0.0; - }, - [](PPCContext* ctx) { - auto result = ctx->f[3]; - REQUIRE(result == 0.0); - }); - test.Run( - [](PPCContext* ctx) { - ctx->f[4] = 5.0; - ctx->f[5] = 7.0; - }, - [](PPCContext* ctx) { - auto result = ctx->f[3]; - REQUIRE(result == 12.0); - }); - test.Run( - [](PPCContext* ctx) { - ctx->f[4] = FLT_MAX / 2.0; - ctx->f[5] = FLT_MAX / 2.0; - }, - [](PPCContext* ctx) { - auto result = ctx->f[3]; - REQUIRE(result == FLT_MAX); - }); - test.Run( - [](PPCContext* ctx) { - ctx->f[4] = -100.0; - ctx->f[5] = -150.0; - }, - [](PPCContext* ctx) { - auto result = ctx->f[3]; - REQUIRE(result == -250.0); - }); - test.Run( - [](PPCContext* ctx) { - ctx->f[4] = FLT_MIN; - ctx->f[5] = 0.0; - }, - [](PPCContext* ctx) { - auto result = ctx->f[3]; - REQUIRE(result == FLT_MIN); - }); - test.Run( - [](PPCContext* ctx) { - ctx->f[4] = FLT_MAX; - ctx->f[5] = 0.0; - }, - [](PPCContext* ctx) { - auto result = ctx->f[3]; - REQUIRE(result == FLT_MAX); - }); -} - TEST_CASE("ADD_F64", "[instr]") { TestFunction test([](HIRBuilder& b) { StoreFPR(b, 3, b.Add(LoadFPR(b, 4), LoadFPR(b, 5))); diff --git a/src/xenia/cpu/testing/f32_test.cc b/src/xenia/cpu/testing/f32_test.cc new file mode 100644 index 000000000..86c9b90ab --- /dev/null +++ b/src/xenia/cpu/testing/f32_test.cc @@ -0,0 +1,281 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2024 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/cpu/testing/util.h" + +#include +#include + +using namespace xe::cpu::hir; +using namespace xe::cpu; +using namespace xe::cpu::testing; +using xe::cpu::ppc::PPCContext; + +// Helper for floating-point comparison with epsilon +static bool ApproxEqual(double a, double b, double epsilon = 1e-6) { + if (std::isnan(a) && std::isnan(b)) return true; + if (std::isinf(a) && std::isinf(b)) return (a > 0) == (b > 0); + return std::abs(a - b) <= epsilon * std::max(std::abs(a), std::abs(b)); +} + +TEST_CASE("ADD_F32", "[instr]") { + TestFunction test([](HIRBuilder& b) { + StoreFPR(b, 3, + b.Convert(b.Add(b.Convert(LoadFPR(b, 4), FLOAT32_TYPE), + b.Convert(LoadFPR(b, 5), FLOAT32_TYPE)), + FLOAT64_TYPE)); + b.Return(); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = 0.0; + ctx->f[5] = 0.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == 0.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = 5.0; + ctx->f[5] = 7.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == 12.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = FLT_MAX / 2.0; + ctx->f[5] = FLT_MAX / 2.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == FLT_MAX); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = -100.0; + ctx->f[5] = -150.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == -250.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = FLT_MIN; + ctx->f[5] = 0.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == FLT_MIN); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = FLT_MAX; + ctx->f[5] = 0.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == FLT_MAX); + }); +} + +TEST_CASE("SUB_F32", "[instr]") { + TestFunction test([](HIRBuilder& b) { + StoreFPR(b, 3, + b.Convert(b.Sub(b.Convert(LoadFPR(b, 4), FLOAT32_TYPE), + b.Convert(LoadFPR(b, 5), FLOAT32_TYPE)), + FLOAT64_TYPE)); + b.Return(); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = 0.0; + ctx->f[5] = 0.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == 0.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = 10.0; + ctx->f[5] = 3.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == 7.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = FLT_MAX; + ctx->f[5] = FLT_MAX / 2.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == FLT_MAX / 2.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = -100.0; + ctx->f[5] = -150.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == 50.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = 5.5; + ctx->f[5] = 2.5; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == 3.0); + }); +} + +TEST_CASE("MUL_F32", "[instr]") { + TestFunction test([](HIRBuilder& b) { + StoreFPR(b, 3, + b.Convert(b.Mul(b.Convert(LoadFPR(b, 4), FLOAT32_TYPE), + b.Convert(LoadFPR(b, 5), FLOAT32_TYPE)), + FLOAT64_TYPE)); + b.Return(); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = 0.0; + ctx->f[5] = 0.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == 0.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = 2.0; + ctx->f[5] = 3.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == 6.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = -5.0; + ctx->f[5] = 3.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == -15.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = -5.0; + ctx->f[5] = -3.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == 15.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = 1.5; + ctx->f[5] = 2.5; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == 3.75); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = 1000000.0f; + ctx->f[5] = 1000000.0f; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(ApproxEqual(result, 1000000000000.0)); + }); +} + +TEST_CASE("DIV_F32", "[instr]") { + TestFunction test([](HIRBuilder& b) { + StoreFPR(b, 3, + b.Convert(b.Div(b.Convert(LoadFPR(b, 4), FLOAT32_TYPE), + b.Convert(LoadFPR(b, 5), FLOAT32_TYPE)), + FLOAT64_TYPE)); + b.Return(); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = 0.0; + ctx->f[5] = 1.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == 0.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = 6.0; + ctx->f[5] = 2.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == 3.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = -15.0; + ctx->f[5] = 3.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == -5.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = -15.0; + ctx->f[5] = -3.0; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == 5.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = 7.5; + ctx->f[5] = 2.5; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == 3.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = 1000000.0f; + ctx->f[5] = 1000.0f; + }, + [](PPCContext* ctx) { + auto result = ctx->f[3]; + REQUIRE(result == 1000.0); + }); + test.Run( + [](PPCContext* ctx) { + ctx->f[4] = 1.0; + ctx->f[5] = 3.0; + }, + [](PPCContext* ctx) { + auto result = static_cast(ctx->f[3]); + REQUIRE(result == 1.0f / 3.0f); + }); +}