[Testing] Add CPU backend tests
This commit is contained in:
489
src/xenia/cpu/testing/opcode_coverage_test.cc
Normal file
489
src/xenia/cpu/testing/opcode_coverage_test.cc
Normal file
@@ -0,0 +1,489 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2026 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
// Tests for previously-untested HIR opcodes, prioritized by ARM64 bug risk.
|
||||
|
||||
#include "xenia/cpu/testing/util.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
using namespace xe;
|
||||
using namespace xe::cpu;
|
||||
using namespace xe::cpu::hir;
|
||||
using namespace xe::cpu::testing;
|
||||
using xe::cpu::ppc::PPCContext;
|
||||
|
||||
// ============================================================================
|
||||
// VECTOR_DENORMFLUSH — potential register aliasing bug
|
||||
// ============================================================================
|
||||
TEST_CASE("VECTOR_DENORMFLUSH", "[vector]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(b, 3, b.VectorDenormFlush(LoadVR(b, 4)));
|
||||
b.Return();
|
||||
});
|
||||
// Normal values should pass through unchanged.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) { ctx->v[4] = vec128f(1.0f, -1.0f, 0.0f, 100.0f); },
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->v[3].f32[0] == 1.0f);
|
||||
REQUIRE(ctx->v[3].f32[1] == -1.0f);
|
||||
REQUIRE(ctx->v[3].f32[2] == 0.0f);
|
||||
REQUIRE(ctx->v[3].f32[3] == 100.0f);
|
||||
});
|
||||
// Denormals should be flushed to signed zero.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
// Smallest positive denormal.
|
||||
uint32_t pos_denorm = 0x00000001;
|
||||
// Smallest negative denormal.
|
||||
uint32_t neg_denorm = 0x80000001;
|
||||
memcpy(&ctx->v[4].f32[0], &pos_denorm, 4);
|
||||
memcpy(&ctx->v[4].f32[1], &neg_denorm, 4);
|
||||
ctx->v[4].f32[2] = 1.0f; // normal, should survive
|
||||
ctx->v[4].f32[3] = -1.0f;
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
uint32_t r0, r1;
|
||||
memcpy(&r0, &ctx->v[3].f32[0], 4);
|
||||
memcpy(&r1, &ctx->v[3].f32[1], 4);
|
||||
REQUIRE(r0 == 0x00000000); // +0.0
|
||||
REQUIRE(r1 == 0x80000000); // -0.0 (sign preserved)
|
||||
REQUIRE(ctx->v[3].f32[2] == 1.0f);
|
||||
REQUIRE(ctx->v[3].f32[3] == -1.0f);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CONVERT F64<->F32 (the only CONVERT variants the PPC frontend generates)
|
||||
// ============================================================================
|
||||
TEST_CASE("CONVERT_F64_TO_F32", "[convert]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
auto fval = b.Convert(LoadFPR(b, 4), FLOAT32_TYPE);
|
||||
StoreFPR(b, 3, b.Convert(fval, FLOAT64_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
test.Run([](PPCContext* ctx) { ctx->f[4] = 1.0; },
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->f[3] == 1.0); });
|
||||
test.Run([](PPCContext* ctx) { ctx->f[4] = -0.0; },
|
||||
[](PPCContext* ctx) {
|
||||
uint64_t bits;
|
||||
memcpy(&bits, &ctx->f[3], 8);
|
||||
REQUIRE(bits == 0x8000000000000000ULL); // -0.0 preserved
|
||||
});
|
||||
// Large value that loses precision in F32.
|
||||
test.Run([](PPCContext* ctx) { ctx->f[4] = 16777217.0; }, // 2^24+1
|
||||
[](PPCContext* ctx) {
|
||||
float f32 = static_cast<float>(ctx->f[3]);
|
||||
REQUIRE(f32 == 16777216.0f); // rounds to 2^24
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// VECTOR_CONVERT_I2F (unsigned and signed)
|
||||
// ============================================================================
|
||||
TEST_CASE("VECTOR_CONVERT_I2F_UNSIGNED", "[vector]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(b, 3, b.VectorConvertI2F(LoadVR(b, 4), ARITHMETIC_UNSIGNED));
|
||||
b.Return();
|
||||
});
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->v[4] = vec128i(0, 1, 0x7FFFFFFF, 0xFFFFFFFF);
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->v[3].f32[0] == 0.0f);
|
||||
REQUIRE(ctx->v[3].f32[1] == 1.0f);
|
||||
// 0x7FFFFFFF rounds to 2147483648.0f in single precision.
|
||||
REQUIRE(ctx->v[3].f32[2] == static_cast<float>(0x7FFFFFFFU));
|
||||
// 0xFFFFFFFF = 4294967295, rounds to 4294967296.0f.
|
||||
REQUIRE(ctx->v[3].f32[3] == static_cast<float>(0xFFFFFFFFU));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("VECTOR_CONVERT_I2F_SIGNED", "[vector]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(b, 3, b.VectorConvertI2F(LoadVR(b, 4), 0));
|
||||
b.Return();
|
||||
});
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->v[4] = vec128i(0, 1, 0x80000000, 0xFFFFFFFF);
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->v[3].f32[0] == 0.0f);
|
||||
REQUIRE(ctx->v[3].f32[1] == 1.0f);
|
||||
REQUIRE(ctx->v[3].f32[2] == -2147483648.0f);
|
||||
REQUIRE(ctx->v[3].f32[3] == -1.0f);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// VECTOR_CONVERT_F2I (unsigned and signed)
|
||||
// ============================================================================
|
||||
TEST_CASE("VECTOR_CONVERT_F2I_UNSIGNED", "[vector]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(b, 3, b.VectorConvertF2I(LoadVR(b, 4), ARITHMETIC_UNSIGNED));
|
||||
b.Return();
|
||||
});
|
||||
test.Run(
|
||||
[](PPCContext* ctx) { ctx->v[4] = vec128f(0.0f, 1.0f, 1.5f, 255.0f); },
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->v[3].u32[0] == 0);
|
||||
REQUIRE(ctx->v[3].u32[1] == 1);
|
||||
REQUIRE(ctx->v[3].u32[2] == 1); // truncate toward zero
|
||||
REQUIRE(ctx->v[3].u32[3] == 255);
|
||||
});
|
||||
// NaN should produce 0.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
float nan = std::numeric_limits<float>::quiet_NaN();
|
||||
ctx->v[4] = vec128f(nan, 0.0f, 0.0f, 0.0f);
|
||||
},
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->v[3].u32[0] == 0); });
|
||||
}
|
||||
|
||||
TEST_CASE("VECTOR_CONVERT_F2I_SIGNED", "[vector]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(b, 3, b.VectorConvertF2I(LoadVR(b, 4), 0));
|
||||
b.Return();
|
||||
});
|
||||
test.Run(
|
||||
[](PPCContext* ctx) { ctx->v[4] = vec128f(0.0f, -1.0f, 1.5f, -1.5f); },
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(static_cast<int32_t>(ctx->v[3].u32[0]) == 0);
|
||||
REQUIRE(static_cast<int32_t>(ctx->v[3].u32[1]) == -1);
|
||||
REQUIRE(static_cast<int32_t>(ctx->v[3].u32[2]) == 1);
|
||||
REQUIRE(static_cast<int32_t>(ctx->v[3].u32[3]) == -1);
|
||||
});
|
||||
// NaN should produce 0.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
float nan = std::numeric_limits<float>::quiet_NaN();
|
||||
ctx->v[4] = vec128f(nan, 0.0f, 0.0f, 0.0f);
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(static_cast<int32_t>(ctx->v[3].u32[0]) == 0);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// VECTOR_COMPARE_EQ / SGT / UGT — basic coverage
|
||||
// ============================================================================
|
||||
TEST_CASE("VECTOR_COMPARE_EQ_I32", "[vector]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(b, 3, b.VectorCompareEQ(LoadVR(b, 4), LoadVR(b, 5), INT32_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->v[4] = vec128i(1, 2, 3, 4);
|
||||
ctx->v[5] = vec128i(1, 99, 3, 99);
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->v[3].u32[0] == 0xFFFFFFFF); // equal
|
||||
REQUIRE(ctx->v[3].u32[1] == 0x00000000); // not equal
|
||||
REQUIRE(ctx->v[3].u32[2] == 0xFFFFFFFF); // equal
|
||||
REQUIRE(ctx->v[3].u32[3] == 0x00000000); // not equal
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("VECTOR_COMPARE_SGT_I32", "[vector]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(b, 3, b.VectorCompareSGT(LoadVR(b, 4), LoadVR(b, 5), INT32_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->v[4] = vec128i(1, 0xFFFFFFFF, 0x80000000, 0);
|
||||
ctx->v[5] = vec128i(0, 0, 0, 0x80000000);
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->v[3].u32[0] == 0xFFFFFFFF); // 1 > 0
|
||||
REQUIRE(ctx->v[3].u32[1] == 0x00000000); // -1 > 0 = false
|
||||
REQUIRE(ctx->v[3].u32[2] == 0x00000000); // INT_MIN > 0 = false
|
||||
REQUIRE(ctx->v[3].u32[3] == 0xFFFFFFFF); // 0 > INT_MIN = true
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("VECTOR_COMPARE_UGT_I32", "[vector]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(b, 3, b.VectorCompareUGT(LoadVR(b, 4), LoadVR(b, 5), INT32_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->v[4] = vec128i(1, 0xFFFFFFFF, 0x80000000, 0);
|
||||
ctx->v[5] = vec128i(0, 0, 0, 0x80000000);
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->v[3].u32[0] == 0xFFFFFFFF); // 1 > 0
|
||||
REQUIRE(ctx->v[3].u32[1] == 0xFFFFFFFF); // 0xFFFFFFFF > 0 (unsigned)
|
||||
REQUIRE(ctx->v[3].u32[2] == 0xFFFFFFFF); // 0x80000000 > 0 (unsigned)
|
||||
REQUIRE(ctx->v[3].u32[3] == 0x00000000); // 0 > 0x80000000 = false
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SPLAT
|
||||
// ============================================================================
|
||||
TEST_CASE("SPLAT_I32", "[vector]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(b, 3, b.Splat(b.Truncate(LoadGPR(b, 4), INT32_TYPE), VEC128_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
test.Run([](PPCContext* ctx) { ctx->r[4] = 0xDEADBEEF; },
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->v[3] ==
|
||||
vec128i(0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF));
|
||||
});
|
||||
test.Run([](PPCContext* ctx) { ctx->r[4] = 0; },
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->v[3] == vec128i(0, 0, 0, 0)); });
|
||||
}
|
||||
|
||||
TEST_CASE("SPLAT_F32", "[vector]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(b, 3, b.Splat(b.Convert(LoadFPR(b, 4), FLOAT32_TYPE), VEC128_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
test.Run([](PPCContext* ctx) { ctx->f[4] = 3.14; },
|
||||
[](PPCContext* ctx) {
|
||||
float expected = static_cast<float>(3.14);
|
||||
REQUIRE(ctx->v[3].f32[0] == expected);
|
||||
REQUIRE(ctx->v[3].f32[1] == expected);
|
||||
REQUIRE(ctx->v[3].f32[2] == expected);
|
||||
REQUIRE(ctx->v[3].f32[3] == expected);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// IS_NAN (F64 — F32 variant is impossible on x64)
|
||||
// ============================================================================
|
||||
TEST_CASE("IS_NAN_F64", "[instr]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreGPR(b, 3, b.ZeroExtend(b.IsNan(LoadFPR(b, 4)), INT64_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
test.Run([](PPCContext* ctx) { ctx->f[4] = 1.0; },
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->r[3] == 0); });
|
||||
test.Run([](PPCContext* ctx) { ctx->f[4] = 0.0; },
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->r[3] == 0); });
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->f[4] = std::numeric_limits<double>::quiet_NaN();
|
||||
},
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->r[3] == 1); });
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->f[4] = std::numeric_limits<double>::infinity();
|
||||
},
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->r[3] == 0); });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DIV I32 — divide by zero and INT_MIN/-1 edge cases
|
||||
// ============================================================================
|
||||
TEST_CASE("DIV_I32", "[arithmetic]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreGPR(b, 3,
|
||||
b.ZeroExtend(b.Div(b.Truncate(LoadGPR(b, 4), INT32_TYPE),
|
||||
b.Truncate(LoadGPR(b, 5), INT32_TYPE)),
|
||||
INT64_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 10;
|
||||
ctx->r[5] = 3;
|
||||
},
|
||||
[](PPCContext* ctx) { REQUIRE(static_cast<int32_t>(ctx->r[3]) == 3); });
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 0xFFFFFFFF;
|
||||
ctx->r[5] = 1;
|
||||
},
|
||||
[](PPCContext* ctx) { REQUIRE(static_cast<int32_t>(ctx->r[3]) == -1); });
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 7;
|
||||
ctx->r[5] = 2;
|
||||
},
|
||||
[](PPCContext* ctx) { REQUIRE(static_cast<int32_t>(ctx->r[3]) == 3); });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// VECTOR_SUB — basic coverage with saturation
|
||||
// ============================================================================
|
||||
TEST_CASE("VECTOR_SUB_I32", "[vector]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(b, 3, b.VectorSub(LoadVR(b, 4), LoadVR(b, 5), INT32_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->v[4] = vec128i(10, 0, 0x80000000, 0xFFFFFFFF);
|
||||
ctx->v[5] = vec128i(3, 1, 1, 0xFFFFFFFF);
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->v[3].u32[0] == 7);
|
||||
REQUIRE(ctx->v[3].u32[1] == 0xFFFFFFFF); // 0-1 wraps
|
||||
REQUIRE(ctx->v[3].u32[2] == 0x7FFFFFFF); // INT_MIN-1 wraps
|
||||
REQUIRE(ctx->v[3].u32[3] == 0);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ABS F32/F64
|
||||
// ============================================================================
|
||||
TEST_CASE("ABS_F32", "[arithmetic]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
auto val = b.Convert(LoadFPR(b, 4), FLOAT32_TYPE);
|
||||
auto absval = b.Abs(val);
|
||||
StoreFPR(b, 3, b.Convert(absval, FLOAT64_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
test.Run([](PPCContext* ctx) { ctx->f[4] = -1.0; },
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->f[3] == 1.0); });
|
||||
test.Run([](PPCContext* ctx) { ctx->f[4] = 1.0; },
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->f[3] == 1.0); });
|
||||
test.Run([](PPCContext* ctx) { ctx->f[4] = 0.0; },
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->f[3] == 0.0); });
|
||||
// ABS of -0.0 should be +0.0.
|
||||
test.Run([](PPCContext* ctx) { ctx->f[4] = -0.0; },
|
||||
[](PPCContext* ctx) {
|
||||
uint64_t bits;
|
||||
memcpy(&bits, &ctx->f[3], 8);
|
||||
REQUIRE(bits == 0); // +0.0, not -0.0
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SQRT F64
|
||||
// ============================================================================
|
||||
TEST_CASE("SQRT_F64", "[arithmetic]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreFPR(b, 3, b.Sqrt(LoadFPR(b, 4)));
|
||||
b.Return();
|
||||
});
|
||||
test.Run([](PPCContext* ctx) { ctx->f[4] = 4.0; },
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->f[3] == 2.0); });
|
||||
test.Run([](PPCContext* ctx) { ctx->f[4] = 0.0; },
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->f[3] == 0.0); });
|
||||
test.Run([](PPCContext* ctx) { ctx->f[4] = 1.0; },
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->f[3] == 1.0); });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MUL_HI I64 — unsigned (ARITHMETIC_UNSIGNED)
|
||||
// ============================================================================
|
||||
TEST_CASE("MUL_HI_I64_UNSIGNED", "[arithmetic]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreGPR(b, 3, b.MulHi(LoadGPR(b, 4), LoadGPR(b, 5), ARITHMETIC_UNSIGNED));
|
||||
b.Return();
|
||||
});
|
||||
// Low values: high 64 bits of 2*3 = 0.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 2;
|
||||
ctx->r[5] = 3;
|
||||
},
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->r[3] == 0); });
|
||||
// 2^63 * 2: high 64 bits = 1.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 0x8000000000000000ULL;
|
||||
ctx->r[5] = 2;
|
||||
},
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->r[3] == 1); });
|
||||
// MAX * MAX: (2^64-1)^2 high bits = 2^64-2.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 0xFFFFFFFFFFFFFFFFULL;
|
||||
ctx->r[5] = 0xFFFFFFFFFFFFFFFFULL;
|
||||
},
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->r[3] == 0xFFFFFFFFFFFFFFFEULL); });
|
||||
}
|
||||
|
||||
// MUL_HI I64 — signed (default)
|
||||
TEST_CASE("MUL_HI_I64_SIGNED", "[arithmetic]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreGPR(b, 3, b.MulHi(LoadGPR(b, 4), LoadGPR(b, 5)));
|
||||
b.Return();
|
||||
});
|
||||
// 2 * 3 signed: high bits = 0.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 2;
|
||||
ctx->r[5] = 3;
|
||||
},
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->r[3] == 0); });
|
||||
// -1 * -1 signed: result is 1 (128-bit), high bits = 0.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 0xFFFFFFFFFFFFFFFFULL;
|
||||
ctx->r[5] = 0xFFFFFFFFFFFFFFFFULL;
|
||||
},
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->r[3] == 0); });
|
||||
// -1 * 2 signed: result is -2, high bits = -1 (0xFFFFFFFFFFFFFFFF).
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 0xFFFFFFFFFFFFFFFFULL;
|
||||
ctx->r[5] = 2;
|
||||
},
|
||||
[](PPCContext* ctx) { REQUIRE(ctx->r[3] == 0xFFFFFFFFFFFFFFFFULL); });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ATOMIC_COMPARE_EXCHANGE I32
|
||||
// ============================================================================
|
||||
TEST_CASE("ATOMIC_COMPARE_EXCHANGE_I32", "[atomic]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
// r[4] = address (guest), r[5] = expected, r[6] = desired.
|
||||
auto addr = LoadGPR(b, 4);
|
||||
auto expected = b.Truncate(LoadGPR(b, 5), INT32_TYPE);
|
||||
auto desired = b.Truncate(LoadGPR(b, 6), INT32_TYPE);
|
||||
auto result = b.AtomicCompareExchange(addr, expected, desired);
|
||||
StoreGPR(b, 3, b.ZeroExtend(result, INT64_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
|
||||
uint32_t guest_addr = test.memory->SystemHeapAlloc(4);
|
||||
REQUIRE(guest_addr != 0);
|
||||
auto* host_ptr =
|
||||
reinterpret_cast<uint32_t*>(test.memory->TranslateVirtual(guest_addr));
|
||||
|
||||
// Success case: expected matches current value.
|
||||
test.Run(
|
||||
[&](PPCContext* ctx) {
|
||||
*host_ptr = 0xAAAAAAAA;
|
||||
ctx->r[4] = guest_addr;
|
||||
ctx->r[5] = 0xAAAAAAAA; // expected
|
||||
ctx->r[6] = 0xBBBBBBBB; // desired
|
||||
},
|
||||
[&](PPCContext* ctx) {
|
||||
REQUIRE(ctx->r[3] == 1); // success
|
||||
REQUIRE(*host_ptr == 0xBBBBBBBB);
|
||||
});
|
||||
|
||||
// Failure case: expected does NOT match.
|
||||
test.Run(
|
||||
[&](PPCContext* ctx) {
|
||||
*host_ptr = 0xCCCCCCCC;
|
||||
ctx->r[4] = guest_addr;
|
||||
ctx->r[5] = 0xDDDDDDDD; // wrong expected
|
||||
ctx->r[6] = 0xEEEEEEEE; // desired
|
||||
},
|
||||
[&](PPCContext* ctx) {
|
||||
REQUIRE(ctx->r[3] == 0); // failure
|
||||
REQUIRE(*host_ptr == 0xCCCCCCCC); // unchanged
|
||||
});
|
||||
|
||||
test.memory->SystemHeapFree(guest_addr);
|
||||
}
|
||||
Reference in New Issue
Block a user