From 00522b56fc9a44888e148e89bf1d9ce9d285abff Mon Sep 17 00:00:00 2001 From: arti Date: Thu, 12 Nov 2026 06:17:29 +0000 Subject: [PATCH] tests: exercise wide 32-bit products and unsigned umul16 Add runtime markers for a full-width 32x32 multiply (0x12345678 * 0x9abcdef0 = 0x242d2080) and for a wide-dividend remainder, so a 16-bit-truncated MPYS or a wrong FS1 cannot still produce the expected small results. Compute the 16x16 unsigned product in unsigned arithmetic to remove the signed-overflow annotation, and reject a regressed nuw nsw multiply in the optimized IR. --- scripts/m4/mul-check.lua | 15 +++++++++------ scripts/m4/mul.c | 13 ++++++++++--- scripts/tests/m4/mul.sh | 11 ++++++++++- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/scripts/m4/mul-check.lua b/scripts/m4/mul-check.lua index fcbb5e5..e8bb30c 100644 --- a/scripts/m4/mul-check.lua +++ b/scripts/m4/mul-check.lua @@ -14,6 +14,8 @@ -- 018A80E0 srem(-100,7) == -2 -- 018A8100 sumto(10) == 55 -- 018A8120 squares(5) == 30 +-- 018A8140 mul(0x12345678,0x9abcdef0) == 0x242d2080 +-- 018A8160 urem(0xfffffffe,7) == 2 dofile(os.getenv("M4_LOADER_LUA")) @@ -28,16 +30,17 @@ emu.register_frame_done(function() checked = true local prog = manager.machine.devices[':maincpu'].spaces['program'] local m = {} - for i = 0, 9 do + for i = 0, 11 do m[i] = prog:read_u32(0x018A8000 + i * 0x20) end print(string.format( - "M4: mul=%d umul16=%d sdiv=%d udiv=%d srem=%d urem=%d sdivneg=%d sremneg=%d sumto=%d squares=%d", - m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9])) + "M4: mul=%d umul16=%d sdiv=%d udiv=%d srem=%d urem=%d sdivneg=%d sremneg=%d sumto=%d squares=%d widemul=%d widerem=%d", + m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], + m[10], m[11])) local want = {42, 0xfffe0001, 14, 613566756, 2, 2, - 0xfffffff2, 0xfffffffe, 55, 30} + 0xfffffff2, 0xfffffffe, 55, 30, 0x242d2080, 2} local ok = true - for i = 0, 9 do + for i = 0, 11 do if m[i] ~= want[i + 1] then ok = false end @@ -45,7 +48,7 @@ emu.register_frame_done(function() if ok then print("M4 MAME: PASS") else - print("M4 MAME: FAIL (want mul=42 umul16=4294836225 sdiv=14 udiv=613566756 srem=2 urem=2 sdivneg=-14 sremneg=-2 sumto=55 squares=30)") + print("M4 MAME: FAIL (want mul=42 umul16=4294836225 sdiv=14 udiv=613566756 srem=2 urem=2 sdivneg=-14 sremneg=-2 sumto=55 squares=30 widemul=606937216 widerem=2)") end end end, "mul-check") diff --git a/scripts/m4/mul.c b/scripts/m4/mul.c index f39aba3..ab1d7ac 100644 --- a/scripts/m4/mul.c +++ b/scripts/m4/mul.c @@ -9,15 +9,20 @@ * 018A80A0 urem(100,7) == 2 * 018A80C0 sdiv(-100,7) == -14 * 018A80E0 srem(-100,7) == -2 + * 018A8140 mul(0x12345678,0x9ABCDEF0) == 0x242D2080 (wide 32x32 product) + * 018A8160 urem(0xfffffffe,7) == 2 (wide quotient in the remainder) */ #define MARK(n) (*(volatile unsigned int *)(0x018A8000u + (n)*0x20u)) int mul(int a, int b) { return a * b; } /* A 16x16 unsigned product: both operands are masked to 16 bits, which is the - * form the target selects to MPYU. A plain unsigned short multiply promotes - * to a signed int multiply and would use MPYS. */ -int umul16(int a, int b) { return (a & 0xffff) * (b & 0xffff); } + * form the target selects to MPYU. The product must be computed in unsigned + * arithmetic: 0xffff * 0xffff does not fit in signed int, so `int * int` would + * be signed overflow and the optimizer could narrow or rename the result. */ +int umul16(int a, int b) { + return (int)((unsigned)(a & 0xffff) * (unsigned)(b & 0xffff)); +} int sdiv(int a, int b) { return a / b; } unsigned udiv(unsigned a, unsigned b) { return a / b; } @@ -33,5 +38,7 @@ int mul_test(void) { MARK(5) = urem(100u, 7u); MARK(6) = (unsigned)sdiv(-100, 7); MARK(7) = (unsigned)srem(-100, 7); + MARK(10) = (unsigned)mul(0x12345678, 0x9ABCDEF0); + MARK(11) = urem(0xfffffffeu, 7u); return 0; } diff --git a/scripts/tests/m4/mul.sh b/scripts/tests/m4/mul.sh index 85190a9..a8dbf46 100755 --- a/scripts/tests/m4/mul.sh +++ b/scripts/tests/m4/mul.sh @@ -34,6 +34,15 @@ if grep -Eq '\bmul i(33|64)\b' "$WORK/asm/loop.ll"; then fi echo " ok driver -O2 loop IR has no i33/i64 multiply" +# The 16x16 unsigned product must not carry the old signed-overflow +# annotation. With the multiply done in unsigned arithmetic the IR is +# `mul nuw`, not `mul nuw nsw`; the latter contradicts the 0xfffe0001 result. +if awk '/^define .*@umul16\(/{infn=1} infn{print} infn && /^}/{exit}' \ + "$WORK/asm/mul.ll" | grep -q 'mul nuw nsw'; then + fail "umul16 still has a signed no-wrap multiply (the product is unsigned)" +fi +echo " ok umul16 multiplies without signed no-wrap" + # Stop at the next function label (a column-0 label that is not an LBB block # label), so a following local function cannot leak into this slice. body() { # body FILE FUNC @@ -49,7 +58,7 @@ body() { # body FILE FUNC TMP=$(mktemp -d "${TMPDIR:-/tmp}/m4-mul.XXXXXX") trap 'rm -rf "$TMP"' EXIT HUP INT TERM -EXPECTED="M4: mul=42 umul16=4294836225 sdiv=14 udiv=613566756 srem=2 urem=2 sdivneg=4294967282 sremneg=4294967294 sumto=55 squares=30" +EXPECTED="M4: mul=42 umul16=4294836225 sdiv=14 udiv=613566756 srem=2 urem=2 sdivneg=4294967282 sremneg=4294967294 sumto=55 squares=30 widemul=606937216 widerem=2" grep -Fqx "$EXPECTED" "$WORK/mame.log" \ || { grep -E '^M4: ' "$WORK/mame.log" >&2 || true; fail "runtime markers did not match: $EXPECTED"; } echo " ok runtime markers: $EXPECTED"