From 5bdd0c41b2d14f2a6f46392660a7627800c64d75 Mon Sep 17 00:00:00 2001 From: Jamiras <32680403+Jamiras@users.noreply.github.com> Date: Sun, 31 Mar 2024 10:56:30 -0600 Subject: [PATCH] add DOUBLE32 type (#325) --- include/rc_runtime_types.h | 2 ++ src/rcheevos/memref.c | 37 +++++++++++++++++++++++++++ src/rcheevos/operand.c | 2 ++ test/rcheevos/test_memref.c | 51 +++++++++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+) diff --git a/include/rc_runtime_types.h b/include/rc_runtime_types.h index d8a7db65..2bda4610 100644 --- a/include/rc_runtime_types.h +++ b/include/rc_runtime_types.h @@ -59,6 +59,8 @@ enum { RC_MEMSIZE_MBF32, RC_MEMSIZE_MBF32_LE, RC_MEMSIZE_FLOAT_BE, + RC_MEMSIZE_DOUBLE32, + RC_MEMSIZE_DOUBLE32_BE, RC_MEMSIZE_VARIABLE }; diff --git a/src/rcheevos/memref.c b/src/rcheevos/memref.c index 87f6ec0b..0cbec67b 100644 --- a/src/rcheevos/memref.c +++ b/src/rcheevos/memref.c @@ -95,6 +95,8 @@ int rc_parse_memref(const char** memaddr, uint8_t* size, uint32_t* address) { switch (*aux++) { case 'f': case 'F': *size = RC_MEMSIZE_FLOAT; break; case 'b': case 'B': *size = RC_MEMSIZE_FLOAT_BE; break; + case 'h': case 'H': *size = RC_MEMSIZE_DOUBLE32; break; + case 'i': case 'I': *size = RC_MEMSIZE_DOUBLE32_BE; break; case 'm': case 'M': *size = RC_MEMSIZE_MBF32; break; case 'l': case 'L': *size = RC_MEMSIZE_MBF32_LE; break; @@ -198,6 +200,29 @@ static void rc_transform_memref_float_be(rc_typed_value_t* value) { value->type = RC_VALUE_TYPE_FLOAT; } +static void rc_transform_memref_double32(rc_typed_value_t* value) +{ + /* decodes the four most significant bytes of an IEEE 754 double into a float */ + const uint32_t mantissa = (value->value.u32 & 0x000FFFFF) << 3; + const int32_t exponent = (int32_t)((value->value.u32 >> 20) & 0x7FF) - 1023; + const int sign = (value->value.u32 & 0x80000000); + value->value.f32 = rc_build_float(mantissa, exponent, sign); + value->type = RC_VALUE_TYPE_FLOAT; +} + +static void rc_transform_memref_double32_be(rc_typed_value_t* value) +{ + /* decodes the four most significant bytes of an IEEE 754 double in big endian format into a float */ + const uint32_t mantissa = (((value->value.u32 & 0xFF000000) >> 24) | + ((value->value.u32 & 0x00FF0000) >> 8) | + ((value->value.u32 & 0x00000F00) << 8)) << 3; + const int32_t exponent = (int32_t)(((value->value.u32 & 0x0000007F) << 4) | + ((value->value.u32 & 0x0000F000) >> 12)) - 1023; + const int sign = (value->value.u32 & 0x00000080); + value->value.f32 = rc_build_float(mantissa, exponent, sign); + value->type = RC_VALUE_TYPE_FLOAT; +} + static void rc_transform_memref_mbf32(rc_typed_value_t* value) { /* decodes a Microsoft Binary Format float */ /* NOTE: 32-bit MBF is stored in memory as big endian (at least for Apple II) */ @@ -322,6 +347,14 @@ void rc_transform_memref_value(rc_typed_value_t* value, uint8_t size) { rc_transform_memref_float_be(value); break; + case RC_MEMSIZE_DOUBLE32: + rc_transform_memref_double32(value); + break; + + case RC_MEMSIZE_DOUBLE32_BE: + rc_transform_memref_double32_be(value); + break; + case RC_MEMSIZE_MBF32: rc_transform_memref_mbf32(value); break; @@ -358,6 +391,8 @@ static const uint32_t rc_memref_masks[] = { 0xffffffff, /* RC_MEMSIZE_MBF32 */ 0xffffffff, /* RC_MEMSIZE_MBF32_LE */ 0xffffffff, /* RC_MEMSIZE_FLOAT_BE */ + 0xffffffff, /* RC_MEMSIZE_DOUBLE32 */ + 0xffffffff, /* RC_MEMSIZE_DOUBLE32_BE*/ 0xffffffff /* RC_MEMSIZE_VARIABLE */ }; @@ -395,6 +430,8 @@ static const uint8_t rc_memref_shared_sizes[] = { RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32 */ RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32_LE */ RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_FLOAT_BE */ + RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_DOUBLE32 */ + RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_DOUBLE32_BE*/ RC_MEMSIZE_32_BITS /* RC_MEMSIZE_VARIABLE */ }; diff --git a/src/rcheevos/operand.c b/src/rcheevos/operand.c index 25225827..65d41dc5 100644 --- a/src/rcheevos/operand.c +++ b/src/rcheevos/operand.c @@ -297,6 +297,8 @@ int rc_operand_is_float_memref(const rc_operand_t* self) { switch (self->size) { case RC_MEMSIZE_FLOAT: case RC_MEMSIZE_FLOAT_BE: + case RC_MEMSIZE_DOUBLE32: + case RC_MEMSIZE_DOUBLE32_BE: case RC_MEMSIZE_MBF32: case RC_MEMSIZE_MBF32_LE: return 1; diff --git a/test/rcheevos/test_memref.c b/test/rcheevos/test_memref.c index 55fcf524..50f3efd3 100644 --- a/test/rcheevos/test_memref.c +++ b/test/rcheevos/test_memref.c @@ -4,6 +4,7 @@ #include "mock_memory.h" #include +#include /* pow */ static void test_mask(char size, uint32_t expected) { @@ -32,6 +33,8 @@ static void test_shared_masks(void) TEST_PARAMS2(test_mask, RC_MEMSIZE_32_BITS_BE, 0xffffffff); TEST_PARAMS2(test_mask, RC_MEMSIZE_FLOAT, 0xffffffff); TEST_PARAMS2(test_mask, RC_MEMSIZE_FLOAT_BE, 0xffffffff); + TEST_PARAMS2(test_mask, RC_MEMSIZE_DOUBLE32, 0xffffffff); + TEST_PARAMS2(test_mask, RC_MEMSIZE_DOUBLE32_BE, 0xffffffff); TEST_PARAMS2(test_mask, RC_MEMSIZE_MBF32, 0xffffffff); TEST_PARAMS2(test_mask, RC_MEMSIZE_VARIABLE, 0xffffffff); } @@ -63,6 +66,8 @@ static void test_shared_sizes(void) TEST_PARAMS2(test_shared_size, RC_MEMSIZE_32_BITS_BE, RC_MEMSIZE_32_BITS); TEST_PARAMS2(test_shared_size, RC_MEMSIZE_FLOAT, RC_MEMSIZE_32_BITS); TEST_PARAMS2(test_shared_size, RC_MEMSIZE_FLOAT_BE, RC_MEMSIZE_32_BITS); + TEST_PARAMS2(test_shared_size, RC_MEMSIZE_DOUBLE32, RC_MEMSIZE_32_BITS); + TEST_PARAMS2(test_shared_size, RC_MEMSIZE_DOUBLE32_BE, RC_MEMSIZE_32_BITS); TEST_PARAMS2(test_shared_size, RC_MEMSIZE_MBF32, RC_MEMSIZE_32_BITS); TEST_PARAMS2(test_shared_size, RC_MEMSIZE_MBF32_LE, RC_MEMSIZE_32_BITS); TEST_PARAMS2(test_shared_size, RC_MEMSIZE_VARIABLE, RC_MEMSIZE_32_BITS); @@ -86,6 +91,29 @@ static void test_transform_float(uint32_t value, uint8_t size, double expected) ASSERT_FLOAT_EQUALS(typed_value.value.f32, expected); } +static double rc_round(double n) +{ + return floor(n + 0.5); /* no round() in c89 */ +} + +static void test_transform_double32(uint32_t value, uint8_t size, double expected) +{ + rc_typed_value_t typed_value; + typed_value.type = RC_VALUE_TYPE_UNSIGNED; + typed_value.value.u32 = value; + rc_transform_memref_value(&typed_value, size); + + /* a 20-bit mantissa only has 6 digits of precision. round to 6 digits, then do a float comparison. */ + if (fabs(expected) != 0.0) { + const double digits = floor(log10(fabs(expected))) + 1; + const double expected_pow = pow(10, 6 - digits); + expected = rc_round(expected * expected_pow) / expected_pow; + typed_value.value.f32 = (float)(rc_round(typed_value.value.f32 * expected_pow) / expected_pow); + } + + ASSERT_FLOAT_EQUALS(typed_value.value.f32, expected); +} + static void test_transform_float_inf(uint32_t value, uint8_t size) { /* C89 does not provide defines for NAN and INFINITY, nor does it provide isnan() or isinf() functions */ @@ -162,6 +190,29 @@ static void test_transforms(void) TEST_PARAMS2(test_transform_float_inf, 0x7F800000, RC_MEMSIZE_FLOAT); TEST_PARAMS2(test_transform_float_nan, 0x7FFFFFFF, RC_MEMSIZE_FLOAT); + TEST_PARAMS3(test_transform_double32, 0x3FF00000, RC_MEMSIZE_DOUBLE32, 1.0); + TEST_PARAMS3(test_transform_double32, 0x4028C000, RC_MEMSIZE_DOUBLE32, 12.375); + TEST_PARAMS3(test_transform_double32, 0x405107DF, RC_MEMSIZE_DOUBLE32, 68.123); + TEST_PARAMS3(test_transform_double32, 0x00000000, RC_MEMSIZE_DOUBLE32, 0.0); + TEST_PARAMS3(test_transform_double32, 0x80000000, RC_MEMSIZE_DOUBLE32, -0.0); + TEST_PARAMS3(test_transform_double32, 0xC0000000, RC_MEMSIZE_DOUBLE32, -2.0); + TEST_PARAMS3(test_transform_double32, 0x400921FB, RC_MEMSIZE_DOUBLE32, 3.14159274101257324); + TEST_PARAMS3(test_transform_double32, 0x3FD55555, RC_MEMSIZE_DOUBLE32, 0.333333334326744076); + TEST_PARAMS3(test_transform_double32, 0x40534892, RC_MEMSIZE_DOUBLE32, 77.133926); + TEST_PARAMS3(test_transform_double32, 0x406A06E1, RC_MEMSIZE_DOUBLE32, 208.214996); + TEST_PARAMS3(test_transform_double32, 0x40B5C6DD, RC_MEMSIZE_DOUBLE32, 5574.863770); + TEST_PARAMS3(test_transform_double32, 0x430C6BF5, RC_MEMSIZE_DOUBLE32, 1000000000000000.0); + TEST_PARAMS3(test_transform_double32, 0x3C9CD2B2, RC_MEMSIZE_DOUBLE32, 0.0000000000000001); + TEST_PARAMS3(test_transform_double32, 0x3780AD01, RC_MEMSIZE_DOUBLE32, 2.39286e-41); + TEST_PARAMS3(test_transform_double32, 0x3FF3C0CA, RC_MEMSIZE_DOUBLE32, 1.234568); + TEST_PARAMS2(test_transform_float_inf, 0x7FF00000, RC_MEMSIZE_DOUBLE32); + TEST_PARAMS2(test_transform_float_nan, 0x7FFFFFFF, RC_MEMSIZE_DOUBLE32); + + TEST_PARAMS3(test_transform_double32, 0x000000C0, RC_MEMSIZE_DOUBLE32_BE, -2.0); + TEST_PARAMS3(test_transform_double32, 0x00003840, RC_MEMSIZE_DOUBLE32_BE, 24.0); + TEST_PARAMS3(test_transform_double32, 0xCAC0F33F, RC_MEMSIZE_DOUBLE32_BE, 1.234568); + TEST_PARAMS3(test_transform_double32, 0xFB210940, RC_MEMSIZE_DOUBLE32_BE, 3.14159274101257324); + TEST_PARAMS3(test_transform_float, 0x0000803F, RC_MEMSIZE_FLOAT_BE, 1.0); TEST_PARAMS3(test_transform_float, 0x00004641, RC_MEMSIZE_FLOAT_BE, 12.375); TEST_PARAMS3(test_transform_float, 0xFA3E8842, RC_MEMSIZE_FLOAT_BE, 68.123);