Skip to content

Commit 07e3334

Browse files
committed
fast-interp: spec-allowed-set tests for q15mulr overflow and madd Inf*0
Two more relaxed-SIMD boundary tests in the unit suite, both exercising implementation-defined behaviors that the dot-product regression-tests already established for this PR but that weren't yet covered for these ops: 1. `q15mulr_int16_min_squared_either_sat_or_wrap` — the INT16_MIN * INT16_MIN case. Spec relaxes the result of `sat_s((a*b + 0x4000) >> 15)` so an implementation may pick either the IEEE/x86 PMULHRSW saturate (0x7fff) or the truncate (0x8000). Test uses *membership* (either of the two allowed values) rather than exact equality, so a future switch to wrap doesn't break the test. 2. `madd_inf_times_zero_propagates_nan` — adversarial input for the fused/unfused FMA path (`f32x4.relaxed_madd`). IEEE 754 §7.2 makes `Inf * 0` an invalid multiply that produces NaN regardless of the subsequent add, so both `fma(Inf, 0, c)` and unfused `Inf * 0 + c` produce *some* NaN — but the specific NaN bit pattern is impl-defined. Test checks each lane against the IEEE-754 NaN predicate (exp == 0xff and fraction != 0) rather than an exact bit pattern. Locally exercised via `iwasm -f`: q15mulr result: 0x7fff (saturate, current SIMDe lowering) madd_inf_times_zero result: 0x7fc00000 per lane (canonical f32 NaN) Both fit the spec-allowed sets the tests describe; the membership assertions confirm without overfitting to the specific bit pattern.
1 parent c80b169 commit 07e3334

1 file changed

Lines changed: 187 additions & 0 deletions

File tree

tests/unit/relaxed-simd/relaxed_simd_test.cc

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,190 @@ TEST_F(RelaxedSimdTest, dot_s_i16_overflow_pin_sibling_op)
284284
wasm_runtime_deinstantiate(inst);
285285
wasm_runtime_unload(module);
286286
}
287+
288+
/*
289+
* Spec-allowed-set test for `i16x8.relaxed_q15mulr_s` at the
290+
* INT16_MIN * INT16_MIN overflow boundary.
291+
*
292+
* (module
293+
* (func (export "q15mulr_int16_min_squared") (result i64)
294+
* v128.const i16x8 -32768 0 0 0 0 0 0 0
295+
* v128.const i16x8 -32768 0 0 0 0 0 0 0
296+
* i16x8.relaxed_q15mulr_s
297+
* i64x2.extract_lane 0))
298+
*
299+
* Q15 multiply-with-rounding: lane = sat_s((a*b + 0x4000) >> 15).
300+
* For a = b = INT16_MIN:
301+
* a*b = (-32768)*(-32768) = 0x40000000
302+
* + 0x4000 = 0x40004000
303+
* >> 15 = 0x8000 = 32768 (overflows i16)
304+
* sat_s = 32767 = 0x7fff (saturate, IEEE/x86 PMULHRSW)
305+
* wrap = (int16)32768 = 0x8000 (truncate, spec-allowed)
306+
*
307+
* The spec's relaxed clause permits either lowering, so the lane-0
308+
* value must be 0x7fff OR 0x8000. Lanes 1..7 are 0 (deterministic).
309+
* Encoded as the low i64 (i64x2.extract_lane 0) the spec-allowed
310+
* set is { 0x0000_0000_0000_7fff, 0x0000_0000_0000_8000 }.
311+
*
312+
* WAMR's hand-rolled lowering picks saturate (0x7fff); this test
313+
* pins the choice via membership rather than exact equality, so a
314+
* future switch to wrap (spec-allowed) does not break the test.
315+
*/
316+
static const uint8_t Q15MULR_OVERFLOW_WASM[] = {
317+
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01,
318+
0x60, 0x00, 0x01, 0x7e, 0x03, 0x02, 0x01, 0x00, 0x07, 0x1d, 0x01,
319+
0x19, 0x71, 0x31, 0x35, 0x6d, 0x75, 0x6c, 0x72, 0x5f, 0x69, 0x6e,
320+
0x74, 0x31, 0x36, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x73, 0x71, 0x75,
321+
0x61, 0x72, 0x65, 0x64, 0x00, 0x00, 0x0a, 0x2e, 0x01, 0x2c, 0x00,
322+
0xfd, 0x0c, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
323+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x0c, 0x00, 0x80,
324+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
325+
0x00, 0x00, 0x00, 0xfd, 0x91, 0x02, 0xfd, 0x1d, 0x00, 0x0b
326+
};
327+
328+
TEST_F(RelaxedSimdTest, q15mulr_int16_min_squared_either_sat_or_wrap)
329+
{
330+
char err[128] = { 0 };
331+
uint8_t buf[sizeof(Q15MULR_OVERFLOW_WASM)];
332+
memcpy(buf, Q15MULR_OVERFLOW_WASM, sizeof(Q15MULR_OVERFLOW_WASM));
333+
334+
wasm_module_t module = wasm_runtime_load(buf, (uint32_t)sizeof(buf), err,
335+
(uint32_t)sizeof(err));
336+
ASSERT_NE(module, nullptr) << "load failed: " << err;
337+
338+
wasm_module_inst_t inst = wasm_runtime_instantiate(
339+
module, 32768u, 32768u, err, (uint32_t)sizeof(err));
340+
ASSERT_NE(inst, nullptr) << "instantiate failed: " << err;
341+
342+
wasm_function_inst_t func =
343+
wasm_runtime_lookup_function(inst, "q15mulr_int16_min_squared");
344+
ASSERT_NE(func, nullptr) << "export `q15mulr_int16_min_squared` not found";
345+
346+
wasm_exec_env_t env = wasm_runtime_create_exec_env(inst, 32768u);
347+
ASSERT_NE(env, nullptr);
348+
349+
uint32_t argv[2] = { 0, 0 };
350+
bool ok = wasm_runtime_call_wasm(env, func, 0, argv);
351+
EXPECT_TRUE(ok) << "call_wasm failed: " << wasm_runtime_get_exception(inst);
352+
353+
/* Lanes 1..3 must be 0 (deterministic). Encoded in argv: lanes
354+
* 1..3 occupy bits 16..63 of the 64-bit packed result.
355+
* argv[0] (low i32) = (lane1 << 16) | lane0
356+
* argv[1] (high i32) = (lane3 << 16) | lane2 */
357+
EXPECT_EQ(argv[1], 0u) << "lanes 2,3 must be zero";
358+
EXPECT_EQ((argv[0] >> 16) & 0xffffu, 0u) << "lane 1 must be zero";
359+
360+
/* Lane 0 = low 16 bits of argv[0]: either 0x7fff (sat) or
361+
* 0x8000 (wrap). Both spec-conformant per the relaxed-SIMD
362+
* implementation-defined clause for q15mulr_s. */
363+
uint32_t lane0 = argv[0] & 0xffffu;
364+
EXPECT_TRUE(lane0 == 0x7fffu || lane0 == 0x8000u)
365+
<< "lane 0 = 0x" << std::hex << lane0
366+
<< ", expected 0x7fff (saturate) or 0x8000 (wrap)";
367+
368+
wasm_runtime_destroy_exec_env(env);
369+
wasm_runtime_deinstantiate(inst);
370+
wasm_runtime_unload(module);
371+
}
372+
373+
/*
374+
* Spec-allowed-set test for `f32x4.relaxed_madd` at the
375+
* (Inf * 0 + c) invalid-multiply boundary.
376+
*
377+
* (module
378+
* (func (export "madd_inf_times_zero_lo") (result i64)
379+
* v128.const f32x4 inf inf inf inf
380+
* v128.const f32x4 0 0 0 0
381+
* v128.const f32x4 1.0 2.0 3.0 4.0
382+
* f32x4.relaxed_madd
383+
* i64x2.extract_lane 0)
384+
* (func (export "madd_inf_times_zero_hi") (result i64) ;; lane 1)
385+
*
386+
* IEEE 754 §7.2: Inf × 0 is an invalid operation and produces NaN
387+
* (regardless of the subsequent add of `c`). Both fused-multiply-
388+
* add (`fma(Inf, 0, c)`) and unfused (`Inf * 0 + c`) lowerings of
389+
* relaxed_madd produce a NaN here — so the choice between them
390+
* doesn't affect the *kind* of result, only its specific bit
391+
* pattern. The relaxed-SIMD spec leaves the NaN bit pattern
392+
* implementation-defined, so the test checks the IEEE-754 NaN
393+
* predicate (exponent all-ones, fraction non-zero) per lane
394+
* rather than an exact bit pattern.
395+
*
396+
* This case is the relevant adversarial input for "do we
397+
* propagate NaN through the FMA path correctly when one of the
398+
* inputs is +Inf and another is +0?" — exactly the kind of
399+
* boundary the spec test set doesn't explicitly cover.
400+
*/
401+
static const uint8_t MADD_INF_TIMES_ZERO_WASM[] = {
402+
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, 0x60,
403+
0x00, 0x01, 0x7e, 0x03, 0x03, 0x02, 0x00, 0x00, 0x07, 0x33, 0x02, 0x16,
404+
0x6d, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x66, 0x5f, 0x74, 0x69, 0x6d,
405+
0x65, 0x73, 0x5f, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x6c, 0x6f, 0x00, 0x00,
406+
0x16, 0x6d, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x66, 0x5f, 0x74, 0x69,
407+
0x6d, 0x65, 0x73, 0x5f, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x68, 0x69, 0x00,
408+
0x01, 0x0a, 0x7f, 0x02, 0x3e, 0x00, 0xfd, 0x0c, 0x00, 0x00, 0x80, 0x7f,
409+
0x00, 0x00, 0x80, 0x7f, 0x00, 0x00, 0x80, 0x7f, 0x00, 0x00, 0x80, 0x7f,
410+
0xfd, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
411+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x0c, 0x00, 0x00, 0x80, 0x3f,
412+
0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x40,
413+
0xfd, 0x85, 0x02, 0xfd, 0x1d, 0x00, 0x0b, 0x3e, 0x00, 0xfd, 0x0c, 0x00,
414+
0x00, 0x80, 0x7f, 0x00, 0x00, 0x80, 0x7f, 0x00, 0x00, 0x80, 0x7f, 0x00,
415+
0x00, 0x80, 0x7f, 0xfd, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
416+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x0c, 0x00,
417+
0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00,
418+
0x00, 0x80, 0x40, 0xfd, 0x85, 0x02, 0xfd, 0x1d, 0x01, 0x0b
419+
};
420+
421+
/* Helper: true iff the f32 bit pattern is any NaN
422+
* (exponent = 0xff, fraction != 0). */
423+
static bool
424+
f32_bits_are_nan(uint32_t bits)
425+
{
426+
uint32_t exp = (bits >> 23) & 0xff;
427+
uint32_t frac = bits & 0x7fffff;
428+
return exp == 0xff && frac != 0u;
429+
}
430+
431+
TEST_F(RelaxedSimdTest, madd_inf_times_zero_propagates_nan)
432+
{
433+
char err[128] = { 0 };
434+
uint8_t buf[sizeof(MADD_INF_TIMES_ZERO_WASM)];
435+
memcpy(buf, MADD_INF_TIMES_ZERO_WASM, sizeof(MADD_INF_TIMES_ZERO_WASM));
436+
437+
wasm_module_t module = wasm_runtime_load(buf, (uint32_t)sizeof(buf), err,
438+
(uint32_t)sizeof(err));
439+
ASSERT_NE(module, nullptr) << "load failed: " << err;
440+
441+
wasm_module_inst_t inst = wasm_runtime_instantiate(
442+
module, 32768u, 32768u, err, (uint32_t)sizeof(err));
443+
ASSERT_NE(inst, nullptr) << "instantiate failed: " << err;
444+
445+
wasm_exec_env_t env = wasm_runtime_create_exec_env(inst, 32768u);
446+
ASSERT_NE(env, nullptr);
447+
448+
/* Call the lo half (lanes 0,1) then the hi half (lanes 2,3);
449+
* each call returns one i64 packing two f32 lanes:
450+
* argv[0] = lane2k bits, argv[1] = lane2k+1 bits */
451+
for (uint32_t half = 0; half < 2; half++) {
452+
const char *name =
453+
half == 0 ? "madd_inf_times_zero_lo" : "madd_inf_times_zero_hi";
454+
wasm_function_inst_t func = wasm_runtime_lookup_function(inst, name);
455+
ASSERT_NE(func, nullptr) << "export `" << name << "` not found";
456+
457+
uint32_t argv[2] = { 0, 0 };
458+
bool ok = wasm_runtime_call_wasm(env, func, 0, argv);
459+
EXPECT_TRUE(ok) << "call_wasm `" << name
460+
<< "` failed: " << wasm_runtime_get_exception(inst);
461+
462+
EXPECT_TRUE(f32_bits_are_nan(argv[0]))
463+
<< name << " lane " << (2 * half) << " not NaN: bits = 0x"
464+
<< std::hex << argv[0];
465+
EXPECT_TRUE(f32_bits_are_nan(argv[1]))
466+
<< name << " lane " << (2 * half + 1) << " not NaN: bits = 0x"
467+
<< std::hex << argv[1];
468+
}
469+
470+
wasm_runtime_destroy_exec_env(env);
471+
wasm_runtime_deinstantiate(inst);
472+
wasm_runtime_unload(module);
473+
}

0 commit comments

Comments
 (0)