blob: cfd4c95ad170642469d7b31d90ea54d4b9a045e1 [file]
/*
* Copyright 2026 The Emscripten Authors. All rights reserved.
* Emscripten is available under two separate licenses, the MIT license and the
* University of Illinois/NCSA Open Source License. Both these licenses can be
* found in the LICENSE file.
*
* Self-contained FMA intrinsics test. Unlike other SSE tests, this does NOT
* compare against native x86 output because FMA emulation (separate mul+add)
* produces different results from true FMA (single rounding) for edge cases
* near overflow/underflow boundaries. Instead, we test with well-behaved values
* where emulated and true FMA agree exactly.
*/
#include <assert.h>
#include <immintrin.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
static int tests_passed = 0;
static int tests_failed = 0;
static void check_f(const char* name, float got, float expected) {
if (got == expected || (isnan(got) && isnan(expected))) {
tests_passed++;
} else {
printf("FAIL %s: got %a, expected %a\n", name, got, expected);
tests_failed++;
}
}
static void check_d(const char* name, double got, double expected) {
if (got == expected || (isnan(got) && isnan(expected))) {
tests_passed++;
} else {
printf("FAIL %s: got %la, expected %la\n", name, got, expected);
tests_failed++;
}
}
/* Bit-exact comparison. Unlike check_f/check_d (which use ==), this
distinguishes +0.0 from -0.0, so it can verify sign-of-zero results. */
static void check_f_bits(const char* name, float got, float expected) {
if (memcmp(&got, &expected, sizeof(float)) == 0) {
tests_passed++;
} else {
printf("FAIL %s: got %a, expected %a\n", name, got, expected);
tests_failed++;
}
}
static void check_d_bits(const char* name, double got, double expected) {
if (memcmp(&got, &expected, sizeof(double)) == 0) {
tests_passed++;
} else {
printf("FAIL %s: got %la, expected %la\n", name, got, expected);
tests_failed++;
}
}
/* Helper to extract float lanes */
static void storeu_ps(float* out, __m128 v) { _mm_storeu_ps(out, v); }
static void storeu_pd(double* out, __m128d v) { _mm_storeu_pd(out, v); }
/* ============================================================
* 128-bit packed float tests
* ============================================================ */
void test_128_ps(void) {
__m128 a = _mm_set_ps(4.0f, 3.0f, 2.0f, 1.0f);
__m128 b = _mm_set_ps(8.0f, 7.0f, 6.0f, 5.0f);
__m128 c = _mm_set_ps(12.0f, 11.0f, 10.0f, 9.0f);
float out[4];
/* fmadd: a*b + c = {1*5+9, 2*6+10, 3*7+11, 4*8+12} = {14, 22, 32, 44} */
storeu_ps(out, _mm_fmadd_ps(a, b, c));
check_f("fmadd_ps[0]", out[0], 14.0f);
check_f("fmadd_ps[1]", out[1], 22.0f);
check_f("fmadd_ps[2]", out[2], 32.0f);
check_f("fmadd_ps[3]", out[3], 44.0f);
/* fmsub: a*b - c = {-4, 2, 10, 20} */
storeu_ps(out, _mm_fmsub_ps(a, b, c));
check_f("fmsub_ps[0]", out[0], -4.0f);
check_f("fmsub_ps[1]", out[1], 2.0f);
check_f("fmsub_ps[2]", out[2], 10.0f);
check_f("fmsub_ps[3]", out[3], 20.0f);
/* fnmadd: -(a*b) + c = {4, -2, -10, -20} */
storeu_ps(out, _mm_fnmadd_ps(a, b, c));
check_f("fnmadd_ps[0]", out[0], 4.0f);
check_f("fnmadd_ps[1]", out[1], -2.0f);
check_f("fnmadd_ps[2]", out[2], -10.0f);
check_f("fnmadd_ps[3]", out[3], -20.0f);
/* fnmsub: -(a*b) - c = {-14, -22, -32, -44} */
storeu_ps(out, _mm_fnmsub_ps(a, b, c));
check_f("fnmsub_ps[0]", out[0], -14.0f);
check_f("fnmsub_ps[1]", out[1], -22.0f);
check_f("fnmsub_ps[2]", out[2], -32.0f);
check_f("fnmsub_ps[3]", out[3], -44.0f);
/* fmaddsub: even=a*b-c, odd=a*b+c = {-4, 22, 10, 44} */
storeu_ps(out, _mm_fmaddsub_ps(a, b, c));
check_f("fmaddsub_ps[0]", out[0], -4.0f);
check_f("fmaddsub_ps[1]", out[1], 22.0f);
check_f("fmaddsub_ps[2]", out[2], 10.0f);
check_f("fmaddsub_ps[3]", out[3], 44.0f);
/* fmsubadd: even=a*b+c, odd=a*b-c = {14, 2, 32, 20} */
storeu_ps(out, _mm_fmsubadd_ps(a, b, c));
check_f("fmsubadd_ps[0]", out[0], 14.0f);
check_f("fmsubadd_ps[1]", out[1], 2.0f);
check_f("fmsubadd_ps[2]", out[2], 32.0f);
check_f("fmsubadd_ps[3]", out[3], 20.0f);
}
/* ============================================================
* 128-bit packed double tests
* ============================================================ */
void test_128_pd(void) {
__m128d a = _mm_set_pd(2.0, 1.0);
__m128d b = _mm_set_pd(4.0, 3.0);
__m128d c = _mm_set_pd(6.0, 5.0);
double out[2];
/* fmadd: {1*3+5, 2*4+6} = {8, 14} */
storeu_pd(out, _mm_fmadd_pd(a, b, c));
check_d("fmadd_pd[0]", out[0], 8.0);
check_d("fmadd_pd[1]", out[1], 14.0);
/* fmsub: {1*3-5, 2*4-6} = {-2, 2} */
storeu_pd(out, _mm_fmsub_pd(a, b, c));
check_d("fmsub_pd[0]", out[0], -2.0);
check_d("fmsub_pd[1]", out[1], 2.0);
/* fnmadd: {-(1*3)+5, -(2*4)+6} = {2, -2} */
storeu_pd(out, _mm_fnmadd_pd(a, b, c));
check_d("fnmadd_pd[0]", out[0], 2.0);
check_d("fnmadd_pd[1]", out[1], -2.0);
/* fnmsub: {-(1*3)-5, -(2*4)-6} = {-8, -14} */
storeu_pd(out, _mm_fnmsub_pd(a, b, c));
check_d("fnmsub_pd[0]", out[0], -8.0);
check_d("fnmsub_pd[1]", out[1], -14.0);
/* fmaddsub: {1*3-5, 2*4+6} = {-2, 14} */
storeu_pd(out, _mm_fmaddsub_pd(a, b, c));
check_d("fmaddsub_pd[0]", out[0], -2.0);
check_d("fmaddsub_pd[1]", out[1], 14.0);
/* fmsubadd: {1*3+5, 2*4-6} = {8, 2} */
storeu_pd(out, _mm_fmsubadd_pd(a, b, c));
check_d("fmsubadd_pd[0]", out[0], 8.0);
check_d("fmsubadd_pd[1]", out[1], 2.0);
}
/* ============================================================
* Scalar float tests — only element 0, upper from a
* ============================================================ */
void test_scalar_ss(void) {
__m128 a = _mm_set_ps(4.0f, 3.0f, 2.0f, 1.0f);
__m128 b = _mm_set_ps(8.0f, 7.0f, 6.0f, 5.0f);
__m128 c = _mm_set_ps(12.0f, 11.0f, 10.0f, 9.0f);
float out[4];
/* fmadd_ss: elem0 = 1*5+9 = 14, upper = {2, 3, 4} from a */
storeu_ps(out, _mm_fmadd_ss(a, b, c));
check_f("fmadd_ss[0]", out[0], 14.0f);
check_f("fmadd_ss[1]", out[1], 2.0f);
check_f("fmadd_ss[2]", out[2], 3.0f);
check_f("fmadd_ss[3]", out[3], 4.0f);
/* fmsub_ss: elem0 = 1*5-9 = -4 */
storeu_ps(out, _mm_fmsub_ss(a, b, c));
check_f("fmsub_ss[0]", out[0], -4.0f);
check_f("fmsub_ss[1]", out[1], 2.0f);
/* fnmadd_ss: elem0 = -(1*5)+9 = 4 */
storeu_ps(out, _mm_fnmadd_ss(a, b, c));
check_f("fnmadd_ss[0]", out[0], 4.0f);
check_f("fnmadd_ss[1]", out[1], 2.0f);
/* fnmsub_ss: elem0 = -(1*5)-9 = -14 */
storeu_ps(out, _mm_fnmsub_ss(a, b, c));
check_f("fnmsub_ss[0]", out[0], -14.0f);
check_f("fnmsub_ss[1]", out[1], 2.0f);
}
/* ============================================================
* Scalar double tests — only element 0, upper from a
* ============================================================ */
void test_scalar_sd(void) {
__m128d a = _mm_set_pd(2.0, 1.0);
__m128d b = _mm_set_pd(4.0, 3.0);
__m128d c = _mm_set_pd(6.0, 5.0);
double out[2];
/* fmadd_sd: elem0 = 1*3+5 = 8, elem1 = 2 from a */
storeu_pd(out, _mm_fmadd_sd(a, b, c));
check_d("fmadd_sd[0]", out[0], 8.0);
check_d("fmadd_sd[1]", out[1], 2.0);
/* fmsub_sd: elem0 = 1*3-5 = -2 */
storeu_pd(out, _mm_fmsub_sd(a, b, c));
check_d("fmsub_sd[0]", out[0], -2.0);
check_d("fmsub_sd[1]", out[1], 2.0);
/* fnmadd_sd: elem0 = -(1*3)+5 = 2 */
storeu_pd(out, _mm_fnmadd_sd(a, b, c));
check_d("fnmadd_sd[0]", out[0], 2.0);
check_d("fnmadd_sd[1]", out[1], 2.0);
/* fnmsub_sd: elem0 = -(1*3)-5 = -8 */
storeu_pd(out, _mm_fnmsub_sd(a, b, c));
check_d("fnmsub_sd[0]", out[0], -8.0);
check_d("fnmsub_sd[1]", out[1], 2.0);
}
/* ============================================================
* Special values: infinities, NaN, signed zero.
*
* Every expected value here is identical whether computed by true fused
* FMA (single rounding) or by the emulated mul+add path (two roundings),
* so this test is valid in both -mrelaxed-simd and -msimd128-only builds.
*
* To keep that property:
* - Infinities come from actual inf inputs (not from overflow), so the
* product is genuinely infinite in both paths.
* - inf - inf = NaN cases use an exact infinite product.
* - Signed-zero results are produced via multiply sign propagation
* (e.g. -0.0 * 1.0), never via product negation, which the emulated
* path implements as 0 - x and would round differently.
* ============================================================ */
void test_special_ps(void) {
const float inf = INFINITY;
float out[4];
/* fmadd: a*b + c
lane0: inf*2 + 1 = +inf
lane1: 2*3 + (-inf) = -inf
lane2: nan*1 + 5 = nan
lane3: inf*1 + (-inf) = nan (inf - inf) */
__m128 a = _mm_set_ps(inf, NAN, 2.0f, inf);
__m128 b = _mm_set_ps(1.0f, 1.0f, 3.0f, 2.0f);
__m128 c = _mm_set_ps(-inf, 5.0f, -inf, 1.0f);
storeu_ps(out, _mm_fmadd_ps(a, b, c));
check_f("special_fmadd_ps[0]", out[0], inf);
check_f("special_fmadd_ps[1]", out[1], -inf);
check_f("special_fmadd_ps[2]", out[2], NAN);
check_f("special_fmadd_ps[3]", out[3], NAN);
/* fmsub: a*b - c
lane0: inf*1 - inf = nan
lane1: 2*3 - 1 = 5
lane2: -0.0*1.0 - 0.0 = -0.0 (signed zero)
lane3: inf*2 - 5 = +inf */
a = _mm_set_ps(inf, -0.0f, 2.0f, inf);
b = _mm_set_ps(2.0f, 1.0f, 3.0f, 1.0f);
c = _mm_set_ps(5.0f, 0.0f, 1.0f, inf);
storeu_ps(out, _mm_fmsub_ps(a, b, c));
check_f("special_fmsub_ps[0]", out[0], NAN);
check_f("special_fmsub_ps[1]", out[1], 5.0f);
check_f_bits("special_fmsub_ps[2]", out[2], -0.0f);
check_f("special_fmsub_ps[3]", out[3], inf);
/* fnmadd: -(a*b) + c
lane0: -(inf*1) + inf = nan
lane1: -(2*3) + 1 = -5
lane2: -(inf*1) + 0 = -inf
lane3: -(2*3) + inf = +inf */
a = _mm_set_ps(2.0f, inf, 2.0f, inf);
b = _mm_set_ps(3.0f, 1.0f, 3.0f, 1.0f);
c = _mm_set_ps(inf, 0.0f, 1.0f, inf);
storeu_ps(out, _mm_fnmadd_ps(a, b, c));
check_f("special_fnmadd_ps[0]", out[0], NAN);
check_f("special_fnmadd_ps[1]", out[1], -5.0f);
check_f("special_fnmadd_ps[2]", out[2], -inf);
check_f("special_fnmadd_ps[3]", out[3], inf);
/* fnmsub: -(a*b) - c
lane0: -(2*3) - 1 = -7
lane1: -(inf*1) - 1 = -inf
lane2: -(inf*1) - inf = -inf
lane3: -(nan*1) - 1 = nan */
a = _mm_set_ps(NAN, inf, inf, 2.0f);
b = _mm_set_ps(1.0f, 1.0f, 1.0f, 3.0f);
c = _mm_set_ps(1.0f, inf, 1.0f, 1.0f);
storeu_ps(out, _mm_fnmsub_ps(a, b, c));
check_f("special_fnmsub_ps[0]", out[0], -7.0f);
check_f("special_fnmsub_ps[1]", out[1], -inf);
check_f("special_fnmsub_ps[2]", out[2], -inf);
check_f("special_fnmsub_ps[3]", out[3], NAN);
}
void test_special_pd(void) {
const double inf = INFINITY;
double out[2];
/* fmadd: lane0: -0.0*1.0 + (-0.0) = -0.0 (signed zero)
lane1: inf*1 + (-inf) = nan */
__m128d a = _mm_set_pd(inf, -0.0);
__m128d b = _mm_set_pd(1.0, 1.0);
__m128d c = _mm_set_pd(-inf, -0.0);
storeu_pd(out, _mm_fmadd_pd(a, b, c));
check_d_bits("special_fmadd_pd[0]", out[0], -0.0);
check_d("special_fmadd_pd[1]", out[1], NAN);
/* fnmadd: lane0: -(inf*1) + 0 = -inf
lane1: -(2*3) + 1 = -5 */
a = _mm_set_pd(2.0, inf);
b = _mm_set_pd(3.0, 1.0);
c = _mm_set_pd(1.0, 0.0);
storeu_pd(out, _mm_fnmadd_pd(a, b, c));
check_d("special_fnmadd_pd[0]", out[0], -inf);
check_d("special_fnmadd_pd[1]", out[1], -5.0);
}
void test_special_scalar(void) {
const float inf = INFINITY;
float out[4];
/* fmadd_ss: lane0 inf*1 + (-inf) = nan; upper lanes pass through from a */
__m128 a = _mm_set_ps(4.0f, 3.0f, 2.0f, inf);
__m128 b = _mm_set_ps(0.0f, 0.0f, 0.0f, 1.0f);
__m128 c = _mm_set_ps(0.0f, 0.0f, 0.0f, -inf);
storeu_ps(out, _mm_fmadd_ss(a, b, c));
check_f("special_fmadd_ss[0]", out[0], NAN);
check_f("special_fmadd_ss[1]", out[1], 2.0f);
check_f("special_fmadd_ss[2]", out[2], 3.0f);
check_f("special_fmadd_ss[3]", out[3], 4.0f);
const double dinf = INFINITY;
double outd[2];
/* fnmadd_sd: lane0 -(inf*1) + 0 = -inf; lane1 passes through from a */
__m128d ad = _mm_set_pd(7.0, dinf);
__m128d bd = _mm_set_pd(0.0, 1.0);
__m128d cd = _mm_set_pd(0.0, 0.0);
storeu_pd(outd, _mm_fnmadd_sd(ad, bd, cd));
check_d("special_fnmadd_sd[0]", outd[0], -dinf);
check_d("special_fnmadd_sd[1]", outd[1], 7.0);
}
/* ============================================================
* 256-bit tests
* ============================================================ */
#ifdef __AVX__
void test_256_ps(void) {
__m256 a = _mm256_set_ps(8.0f, 7.0f, 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f);
__m256 b =
_mm256_set_ps(16.0f, 15.0f, 14.0f, 13.0f, 12.0f, 11.0f, 10.0f, 9.0f);
__m256 c =
_mm256_set_ps(24.0f, 23.0f, 22.0f, 21.0f, 20.0f, 19.0f, 18.0f, 17.0f);
float out[8];
/* fmadd: a*b+c = {1*9+17, 2*10+18, 3*11+19, 4*12+20, 5*13+21, 6*14+22,
7*15+23, 8*16+24} = {26, 38, 52, 68, 86, 106, 128, 152} */
_mm256_storeu_ps(out, _mm256_fmadd_ps(a, b, c));
check_f("256_fmadd_ps[0]", out[0], 26.0f);
check_f("256_fmadd_ps[1]", out[1], 38.0f);
check_f("256_fmadd_ps[2]", out[2], 52.0f);
check_f("256_fmadd_ps[3]", out[3], 68.0f);
check_f("256_fmadd_ps[4]", out[4], 86.0f);
check_f("256_fmadd_ps[5]", out[5], 106.0f);
check_f("256_fmadd_ps[6]", out[6], 128.0f);
check_f("256_fmadd_ps[7]", out[7], 152.0f);
/* fmsub: a*b-c */
_mm256_storeu_ps(out, _mm256_fmsub_ps(a, b, c));
check_f("256_fmsub_ps[0]", out[0], -8.0f); /* 1*9-17 */
check_f("256_fmsub_ps[7]", out[7], 104.0f); /* 8*16-24 */
/* fnmadd: -(a*b)+c */
_mm256_storeu_ps(out, _mm256_fnmadd_ps(a, b, c));
check_f("256_fnmadd_ps[0]", out[0], 8.0f); /* -(1*9)+17 */
check_f("256_fnmadd_ps[7]", out[7], -104.0f); /* -(8*16)+24 */
/* fnmsub: -(a*b)-c */
_mm256_storeu_ps(out, _mm256_fnmsub_ps(a, b, c));
check_f("256_fnmsub_ps[0]", out[0], -26.0f); /* -(1*9)-17 */
check_f("256_fnmsub_ps[7]", out[7], -152.0f); /* -(8*16)-24 */
/* fmaddsub: even=a*b-c, odd=a*b+c */
_mm256_storeu_ps(out, _mm256_fmaddsub_ps(a, b, c));
check_f("256_fmaddsub_ps[0]", out[0], -8.0f); /* even: sub */
check_f("256_fmaddsub_ps[1]", out[1], 38.0f); /* odd: add */
check_f("256_fmaddsub_ps[2]", out[2], 14.0f); /* even: sub */
check_f("256_fmaddsub_ps[3]", out[3], 68.0f); /* odd: add */
/* fmsubadd: even=a*b+c, odd=a*b-c */
_mm256_storeu_ps(out, _mm256_fmsubadd_ps(a, b, c));
check_f("256_fmsubadd_ps[0]", out[0], 26.0f); /* even: add */
check_f("256_fmsubadd_ps[1]", out[1], 2.0f); /* odd: sub */
}
void test_256_pd(void) {
__m256d a = _mm256_set_pd(4.0, 3.0, 2.0, 1.0);
__m256d b = _mm256_set_pd(8.0, 7.0, 6.0, 5.0);
__m256d c = _mm256_set_pd(12.0, 11.0, 10.0, 9.0);
double out[4];
/* fmadd: {1*5+9, 2*6+10, 3*7+11, 4*8+12} = {14, 22, 32, 44} */
_mm256_storeu_pd(out, _mm256_fmadd_pd(a, b, c));
check_d("256_fmadd_pd[0]", out[0], 14.0);
check_d("256_fmadd_pd[1]", out[1], 22.0);
check_d("256_fmadd_pd[2]", out[2], 32.0);
check_d("256_fmadd_pd[3]", out[3], 44.0);
/* fmsub */
_mm256_storeu_pd(out, _mm256_fmsub_pd(a, b, c));
check_d("256_fmsub_pd[0]", out[0], -4.0);
check_d("256_fmsub_pd[3]", out[3], 20.0);
/* fnmadd */
_mm256_storeu_pd(out, _mm256_fnmadd_pd(a, b, c));
check_d("256_fnmadd_pd[0]", out[0], 4.0);
/* fnmsub */
_mm256_storeu_pd(out, _mm256_fnmsub_pd(a, b, c));
check_d("256_fnmsub_pd[0]", out[0], -14.0);
/* fmaddsub: even=a*b-c, odd=a*b+c */
_mm256_storeu_pd(out, _mm256_fmaddsub_pd(a, b, c));
check_d("256_fmaddsub_pd[0]", out[0], -4.0); /* even: sub */
check_d("256_fmaddsub_pd[1]", out[1], 22.0); /* odd: add */
/* fmsubadd: even=a*b+c, odd=a*b-c */
_mm256_storeu_pd(out, _mm256_fmsubadd_pd(a, b, c));
check_d("256_fmsubadd_pd[0]", out[0], 14.0); /* even: add */
check_d("256_fmsubadd_pd[1]", out[1], 2.0); /* odd: sub */
}
#endif
/* ============================================================
* Additional edge case tests with fractional values
* ============================================================ */
void test_fractional(void) {
/* Use values that exercise non-trivial math but stay in exact float range */
__m128 a = _mm_set_ps(0.5f, -1.5f, 2.25f, -0.75f);
__m128 b = _mm_set_ps(4.0f, -2.0f, 0.5f, 8.0f);
__m128 c = _mm_set_ps(1.0f, 3.0f, -1.0f, 0.5f);
float out[4];
/* fmadd: {-0.75*8+0.5, 2.25*0.5-1, -1.5*-2+3, 0.5*4+1}
= {-5.5, 0.125, 6, 3} */
storeu_ps(out, _mm_fmadd_ps(a, b, c));
check_f("frac_fmadd[0]", out[0], -5.5f);
check_f("frac_fmadd[1]", out[1], 0.125f);
check_f("frac_fmadd[2]", out[2], 6.0f);
check_f("frac_fmadd[3]", out[3], 3.0f);
__m128d ad = _mm_set_pd(-0.125, 3.5);
__m128d bd = _mm_set_pd(16.0, 2.0);
__m128d cd = _mm_set_pd(1.0, -3.0);
double outd[2];
/* fmadd: {3.5*2-3, -0.125*16+1} = {4, -1} */
storeu_pd(outd, _mm_fmadd_pd(ad, bd, cd));
check_d("frac_fmadd_pd[0]", outd[0], 4.0);
check_d("frac_fmadd_pd[1]", outd[1], -1.0);
}
int main() {
printf("Testing 128-bit packed float (ps)...\n");
test_128_ps();
printf("Testing 128-bit packed double (pd)...\n");
test_128_pd();
printf("Testing scalar float (ss)...\n");
test_scalar_ss();
printf("Testing scalar double (sd)...\n");
test_scalar_sd();
#ifdef __AVX__
printf("Testing 256-bit packed float (ps)...\n");
test_256_ps();
printf("Testing 256-bit packed double (pd)...\n");
test_256_pd();
#endif
printf("Testing fractional values...\n");
test_fractional();
printf("Testing special values (inf, nan, signed zero)...\n");
test_special_ps();
test_special_pd();
test_special_scalar();
printf("\nResults: %d passed, %d failed\n", tests_passed, tests_failed);
if (tests_failed == 0) {
printf("All FMA tests PASSED\n");
}
assert(tests_failed == 0);
return 0;
}