From ce2f20c26b4f2b66c85d7cc90d0571f5b00f89b7 Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Mon, 17 Aug 2026 15:48:45 -0400 Subject: [PATCH] test/relaxed_dot: add i16-intermediate overflow boundary cases The existing relaxed dot-product cases all use byte values whose i16 pair sums stay within i16 range, so an implementation that skips the i16 intermediate entirely (summing four byte products directly into i32 in the add variant) produces identical results on every existing input and the deviation is unobservable. Add a = b = -128 cases where each pair sum is 32768, one past the i16 maximum: for i16x8.relaxed_dot_i8x16_i7x16_s every allowed interpretation yields either -32768 (wrapping) or 32767 (saturating), and for i32x4.relaxed_dot_i8x16_i7x16_add_s the allowed lane values are -65536, -1, or 65534 -- while the no-i16-intermediate shape produces 65536, outside the set, so the boundary is now observable. Ported from WebAssembly/relaxed-simd#164 per maintainer guidance that the proposal repository is merged and inactive. --- .../relaxed-simd/relaxed_dot_product.wast | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/test/core/relaxed-simd/relaxed_dot_product.wast b/test/core/relaxed-simd/relaxed_dot_product.wast index 41dee0afcf..ea541ae1f2 100644 --- a/test/core/relaxed-simd/relaxed_dot_product.wast +++ b/test/core/relaxed-simd/relaxed_dot_product.wast @@ -37,6 +37,30 @@ (v128.const i16x8 32512 0 0 0 0 0 0 0) (v128.const i16x8 33024 0 0 0 0 0 0 0))) +;; i16-intermediate overflow boundary. The existing cases above +;; pick byte values where the pair sum fits in i16 (e.g. +;; -128*-127*2 = 32512, within range). With a = b = -128 every +;; pair sum equals -128*-128 + -128*-128 = 32768, OUT of i16. +;; Per the spec the i16 lane is the relaxed truncation of that +;; pair sum, so for every interpretation the lane is either +;; -32768 (wrap) or 32767 (saturate). +;; +;; signed * signed wrap : (int16)32768 = -32768 +;; signed * signed saturate : clamp(32768) = 32767 +;; signed * unsigned (PMADDUBSW saturate) : -128 * 128 * 2 +;; = -32768 saturated +;; = -32768 +;; unsigned * unsigned wrap : (int16)32768 = -32768 +;; unsigned * unsigned sat : clamp(32768) = 32767 +(assert_return (invoke "i16x8.relaxed_dot_i8x16_i7x16_s" + (v128.const i8x16 -128 -128 -128 -128 -128 -128 -128 -128 + -128 -128 -128 -128 -128 -128 -128 -128) + (v128.const i8x16 -128 -128 -128 -128 -128 -128 -128 -128 + -128 -128 -128 -128 -128 -128 -128 -128)) + (either + (v128.const i16x8 -32768 -32768 -32768 -32768 -32768 -32768 -32768 -32768) + (v128.const i16x8 32767 32767 32767 32767 32767 32767 32767 32767))) + ;; Simple values to ensure things are functional. (assert_return (invoke "i32x4.relaxed_dot_i8x16_i7x16_add_s" (v128.const i8x16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15) @@ -69,6 +93,38 @@ (v128.const i32x4 65025 2 3 4) (v128.const i32x4 66049 2 3 4))) +;; i16-intermediate overflow boundary for the add variant. The +;; existing cases above all choose byte values where the i16 +;; pair sum fits in i16 (e.g. -128*-127*2 = 32512, within range), +;; so an implementation that skips the i16 intermediate and sums +;; four byte products directly into i32 will produce the same +;; answer on those inputs and the bug stays hidden. With +;; a = b = -128 every pair sum is 32768, which overflows i16: +;; +;; per pair: wrap -> -32768 | saturate -> 32767 +;; +;; extadd_pairwise then sums the two pair-i16s into i32 (no +;; further overflow possible), and the final + c wraps in i32. +;; With c = 0 the spec-allowed i32 lane values are: +;; +;; wrap+wrap = -65536 (PMADDUBSW + extadd, or s*s wrap, etc.) +;; sat+sat = 65534 (s*s with saturating intermediate) +;; wrap+sat = -1 +;; +;; A direct-byte-sum implementation (the bug shape, no i16 +;; truncation point at all) produces 65536 per lane, which is +;; NOT in this set. +(assert_return (invoke "i32x4.relaxed_dot_i8x16_i7x16_add_s" + (v128.const i8x16 -128 -128 -128 -128 -128 -128 -128 -128 + -128 -128 -128 -128 -128 -128 -128 -128) + (v128.const i8x16 -128 -128 -128 -128 -128 -128 -128 -128 + -128 -128 -128 -128 -128 -128 -128 -128) + (v128.const i32x4 0 0 0 0)) + (either + (v128.const i32x4 -65536 -65536 -65536 -65536) + (v128.const i32x4 -1 -1 -1 -1) + (v128.const i32x4 65534 65534 65534 65534))) + ;; Check that multiple calls to the relaxed instruction with same inputs returns same results. ;; Test max and min i8 values;