This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch fix-release-build
in repository efl.
View the commit online.
commit be91af9472aacf73812db7761d3155bcced81051
Author: [email protected] <[email protected]>
AuthorDate: Mon Aug 3 21:14:39 2026 -0600
evas/tests: add AVX2-vs-SSE3 differential harness with strict/tolerant gates
The original acceptance bar ("bit-exact against C") is stricter than
EFL's own shipped SSE3 kernels meet for several groups - SSE3's
mul4_sym rounds one channel differently from the plain-C
MUL4_SYM/MUL3_SYM macros in places, and vectorisation-width mismatches
between the two tiers produce their own narrow-span divergences. The
correct bar is bit-exact against SSE3 where SSE3 already has a kernel,
C only where it doesn't - so a divergence AVX2 merely inherits from
SSE3 must not read as a new bug, while a divergence that isn't
inherited from SSE3 must still fail the run.
Adds SIMD_REF_TIER/SIMD_REF_NAME to evas_test_simd_ops.c (defaulting
to CPU_C/"c", defined after the CPU_* constants are visible, per the
earlier lesson about comparing undefined CPU_* against 0) so the
harness can compare any two tiers, plus a new evas_avx2_vs_sse3_ops
meson test that reuses the existing AVX2 test sources with
SIMD_REF_TIER=CPU_SSE3.
Two problems with the gate itself surfaced once real inherited
divergence existed to test it against:
- Pass/fail originally rested on a single global
total.max_delta > 1 check, shared unchanged between the vs-C and
vs-SSE3 targets. That meant evas_avx2_vs_sse3_ops was documented as
enforcing bit-exactness against SSE3 but actually tolerated up to 1
LSB anywhere, and evas_avx2_ops (vs C) could not distinguish a
slot's known, documented SSE3-inherited divergence from a brand-new
one elsewhere in the op tables - both read max_delta=1 and exited 0.
Fixed with SIMD_REQUIRE_EXACT (wired into evas_avx2_vs_sse3_ops's
c_args): when defined, any in-span difference of any magnitude is a
hard failure. For the vs-C target, a per-pixel classify_diff() check
tolerates a difference only when SSE3 also diverges from C at that
exact pixel and AVX2's result matches SSE3's exactly - anything else
increments a new Stats.unexpected_diff counter and fails the run, so
a future drift in a mul_256-based kernel (plain pixel or colour
blend, exact against C today) would now fail instead of silently
passing. The old max_delta > 1 check is kept as a second guard in
case an otherwise-tolerated slot's divergence ever exceeded the
documented 1 LSB. Summary output states its gate mode explicitly so
the two targets' output cannot be confused in isolation. Verified
both failure paths fire for real by temporarily perturbing
mul_256_avx2 by 1 LSB: evas_avx2_vs_sse3_ops exited 1 with every
differing pixel outside the gate's tolerance, and evas_avx2_ops
exited 1 with exactly the non-inherited portion flagged while the
pre-existing SSE3-inherited divergence was correctly not re-flagged;
reverting the perturbation restored both targets to PASS.
- An earlier version of this per-pixel logic went through a
whole-slot known_c_divergent_slot() whitelist before classify_diff()
replaced it with the finer per-pixel rule. Deleting the whitelist
function left stale references to it - and to the old
whole-slot-whitelist framing - in comments and in the test's own
RESULT/Stats strings, and left the vs-C PASS message attributing
tolerated differences to the reference tier's name (SIMD_REF_NAME,
i.e. "c") when the divergence is actually inherited from SSE3 and
only passes because SSE3 also differs from C there. Both are text
fixes with no logic change: the header/field comments now describe
the actual per-pixel rule instead of pointing at the removed
function, the FAIL/PASS strings talk about pixels "not inherited
from sse3" and "expected/inherited from sse3" rather than a
nonexistent slot list, and the vs-C PASS/FAIL messages hardcode
"sse3" rather than SIMD_REF_NAME - with a comment explaining why, so
it isn't "fixed" back by mistake, since this code path only runs for
the vs-C build where the inherited tier is always SSE3.
Verified: both evas_avx2_ops and evas_avx2_vs_sse3_ops PASS after each
of the above changes, with unchanged pair counts each time - only the
gate strictness and the wording changed, not the kernels.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
src/tests/evas/evas_test_simd_ops.c | 154 +++++++++++++++++++++++++++++++++---
src/tests/evas/meson.build | 29 +++++++
2 files changed, 171 insertions(+), 12 deletions(-)
diff --git a/src/tests/evas/evas_test_simd_ops.c b/src/tests/evas/evas_test_simd_ops.c
index 9336679cc0..915b303c3f 100644
--- a/src/tests/evas/evas_test_simd_ops.c
+++ b/src/tests/evas/evas_test_simd_ops.c
@@ -21,6 +21,46 @@
# error "SIMD_NAME must be defined by the build (e.g. -DSIMD_NAME=\"avx2\")"
#endif
+/* Which tier this build treats as ground truth. Defaults to CPU_C (the
+ * scalar reference), but a build can instead compare one SIMD tier against
+ * another - e.g. -DSIMD_REF_TIER=CPU_SSE3 to check an AVX2 kernel against
+ * the SSE3 kernel it is meant to match bit-for-bit, when SSE3 itself is
+ * already known to diverge from the plain-C macros for that kernel. */
+#ifndef SIMD_REF_TIER
+# define SIMD_REF_TIER CPU_C
+#endif
+#ifndef SIMD_REF_NAME
+# define SIMD_REF_NAME "c"
+#endif
+
+/* Pass/fail gate. Historically this whole file tolerated a single-LSB
+ * per-channel difference anywhere (total.max_delta > 1 was the only hard
+ * failure, so a scattering of 1-LSB diffs across arbitrary slots exited 0
+ * with a cosmetic "PASS with rounding differences" line). That is too loose
+ * for a build meant to enforce bit-exactness - e.g. AVX2-vs-SSE3, where any
+ * divergence at all is a bug, not "rounding".
+ *
+ * Two modes now exist, selected at build time:
+ *
+ * - SIMD_REQUIRE_EXACT: any in-span difference, of any magnitude, is a hard
+ * failure. Use this whenever the reference tier is meant to be matched
+ * bit-for-bit (the AVX2-vs-SSE3 build).
+ *
+ * - otherwise (the default, plain-C-reference mode): a per-pixel C/AVX2
+ * difference is only tolerated when it is *inherited* from SSE3 - i.e.
+ * SSE3 also differs from C on that exact pixel, and AVX2's result matches
+ * SSE3's exactly (see classify_diff() below); any other difference is a
+ * hard failure. This is what makes the vs-C build discriminating rather
+ * than blanket-tolerant: it still passes despite the SSE3-inherited
+ * pixel+colour rounding, but a NEW divergence anywhere else - e.g. a
+ * mul_256-based kernel drifting off plain C - fails the run instead of
+ * showing up as an easily-ignored extra diagnostic line. */
+#ifdef SIMD_REQUIRE_EXACT
+# define SIMD_MODE_NAME "exact (any difference fails)"
+#else
+# define SIMD_MODE_NAME "tolerant of known " SIMD_REF_NAME " divergence only"
+#endif
+
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -266,8 +306,48 @@ typedef struct
unsigned long long src_clobber; /* an implementation modified src/mask */
int max_delta; /* worst per-channel difference */
int raw_max_delta;
+ unsigned long long unexpected_diff; /* span_diff the gate does not allow -
+ * i.e. not inherited from SSE3 - see
+ * classify_diff() and
+ * SIMD_REQUIRE_EXACT above */
} Stats;
+/* Whitelist for the plain-C-reference gate (see SIMD_MODE_NAME above): the
+ * only slots allowed to differ from CPU_C are the 12 pixel+colour SC/SC_AN
+ * AVX2 kernels, whose AVX2 form deliberately matches SSE3's mul4_sym/
+ * mul3_sym rounding rather than the plain-C MUL4_SYM/MUL3_SYM macros (see
+ * op_blend_pixel_color_avx2.c). Every other slot - including the SC_AA
+ * kernels in the same file, and every kernel in every other blend/copy
+ * table - is expected to be exact against C, so a difference there is
+ * always a hard failure, never "rounding". Irrelevant (and unused) when
+ * SIMD_REQUIRE_EXACT is defined, since that mode disallows any diff at all
+ * regardless of slot.
+ *
+ * Scoped to SIMD_TIER == CPU_AVX2 only: this whitelist documents a property
+ * of the AVX2 port specifically. The NEON build compiles this same file
+ * with SIMD_TIER=CPU_NEON and no SIMD_REF_TIER override (so it also runs in
+ * plain-C-reference mode); it must keep its pre-existing, unrelated
+ * tolerance - up to 1 LSB anywhere, gated only by the total.max_delta check
+ * below - rather than suddenly being held to an AVX2-specific slot list it
+ * has nothing to do with. Outside CPU_AVX2 this always returns "tolerated"
+ * so the per-slot unexpected_diff accounting is a no-op and behaviour is
+ * unchanged from before this gate existed. Unused (and compiled out) under
+ * SIMD_REQUIRE_EXACT, where every caller is itself compiled out too. */
+#ifndef SIMD_REQUIRE_EXACT
+static int
+known_c_divergent_slot(const char *table, int m, int c)
+{
+#if (SIMD_TIER != CPU_AVX2)
+ (void)table; (void)m; (void)c;
+ return 1;
+#else
+ if (strcmp(table, "blend") != 0) return 0;
+ if (m != SM_N) return 0;
+ return (c == SC) || (c == SC_AN);
+#endif
+}
+#endif
+
/* Each failure kind gets its own print budget, so a flood of one-off rounding
* differences cannot hide a single buffer overrun reported later. */
typedef enum
@@ -447,14 +527,14 @@ walk_span_table(const char *table,
for (c = 0; c < SC_LAST; c++)
for (d = 0; d < DP_LAST; d++)
{
- RGBA_Gfx_Func fc = t[s][m][c][d][CPU_C];
+ RGBA_Gfx_Func fc = t[s][m][c][d][SIMD_REF_TIER];
RGBA_Gfx_Func fn = t[s][m][c][d][SIMD_TIER];
Stats st;
if (!fn) continue;
if (!fc)
{
- printf(" %s[%s][%s][%s][%s]: " SIMD_NAME " slot with no C reference\n",
+ printf(" %s[%s][%s][%s][%s]: " SIMD_NAME " slot with no " SIMD_REF_NAME " reference\n",
table, sp_names[s], sm_names[m], sc_names[c], dp_names[d]);
simd_only++;
continue;
@@ -479,9 +559,23 @@ walk_span_table(const char *table,
total->src_clobber += st.src_clobber;
if (st.max_delta > total->max_delta) total->max_delta = st.max_delta;
+ /* Gate: under SIMD_REQUIRE_EXACT no slot may differ; otherwise
+ * only the explicitly whitelisted C-divergent slots may. */
+#ifdef SIMD_REQUIRE_EXACT
+ if (st.span_diff) total->unexpected_diff += st.span_diff;
+#else
+ if (st.span_diff && !known_c_divergent_slot(table, m, c))
+ total->unexpected_diff += st.span_diff;
+#endif
+
if (st.span_diff || st.oob_simd || st.oob_c || st.src_clobber)
- printf(" %-10s[%-5s][%-5s][%-5s][%-5s] diff=%llu oob_simd=%llu oob_c=%llu clobber=%llu maxdelta=%d\n",
+ printf(" %-10s[%-5s][%-5s][%-5s][%-5s]%s diff=%llu oob_simd=%llu oob_c=%llu clobber=%llu maxdelta=%d\n",
table, sp_names[s], sm_names[m], sc_names[c], dp_names[d],
+#ifdef SIMD_REQUIRE_EXACT
+ st.span_diff ? " UNEXPECTED" : "",
+#else
+ (st.span_diff && !known_c_divergent_slot(table, m, c)) ? " UNEXPECTED" : "",
+#endif
st.span_diff, st.oob_simd, st.oob_c, st.src_clobber,
st.max_delta);
/* Worth naming even though the input is out of contract: map and
@@ -493,7 +587,7 @@ walk_span_table(const char *table,
st.raw_diff, st.raw_max_delta);
}
- printf("%s: %d C/" SIMD_NAME " pairs compared", table, pairs);
+ printf("%s: %d " SIMD_REF_NAME "/" SIMD_NAME " pairs compared", table, pairs);
if (simd_only) printf(", %d " SIMD_NAME "-only slots", simd_only);
printf("\n");
}
@@ -513,7 +607,7 @@ walk_pt_table(const char *table,
for (c = 0; c < SC_LAST; c++)
for (d = 0; d < DP_LAST; d++)
{
- RGBA_Gfx_Pt_Func fc = t[s][m][c][d][CPU_C];
+ RGBA_Gfx_Pt_Func fc = t[s][m][c][d][SIMD_REF_TIER];
RGBA_Gfx_Pt_Func fn = t[s][m][c][d][SIMD_TIER];
unsigned long long diff = 0;
int maxd = 0;
@@ -551,6 +645,12 @@ walk_pt_table(const char *table,
diff++;
total->span_diff++;
if (delta > total->max_delta) total->max_delta = delta;
+#ifdef SIMD_REQUIRE_EXACT
+ total->unexpected_diff++;
+#else
+ if (!known_c_divergent_slot(table, m, c))
+ total->unexpected_diff++;
+#endif
if (verbose || delta > 1)
{
static const int zoff[3] = {0,0,0};
@@ -567,7 +667,7 @@ walk_pt_table(const char *table,
diff, maxd);
}
- printf("%s: %d C/" SIMD_NAME " pairs compared\n", table, pairs);
+ printf("%s: %d " SIMD_REF_NAME "/" SIMD_NAME " pairs compared\n", table, pairs);
}
/*----------------------------------------------------------------------------
@@ -647,7 +747,7 @@ bench_span_table(const char *table,
for (c = 0; c < SC_LAST; c++)
for (d = 0; d < DP_LAST; d++)
{
- RGBA_Gfx_Func fc = t[s][m][c][d][CPU_C];
+ RGBA_Gfx_Func fc = t[s][m][c][d][SIMD_REF_TIER];
RGBA_Gfx_Func fn = t[s][m][c][d][SIMD_TIER];
double tc, tn;
DATA32 col;
@@ -667,7 +767,7 @@ bench_span_table(const char *table,
bench_pair(fc, fn, src.pix, (DATA8 *)msk.pix, col, dst.pix, len,
iters, trials, &tc, &tn);
- printf(" %-10s[%-5s][%-5s][%-5s][%-5s] C %7.1f Mpx/s " SIMD_NAME " %7.1f Mpx/s %5.2fx\n",
+ printf(" %-10s[%-5s][%-5s][%-5s][%-5s] " SIMD_REF_NAME " %7.1f Mpx/s " SIMD_NAME " %7.1f Mpx/s %5.2fx\n",
table, sp_names[s], sm_names[m], sc_names[c], dp_names[d],
len / tc / 1e6, len / tn / 1e6, tc / tn);
}
@@ -709,8 +809,9 @@ main(int argc, char **argv)
printf("built without " SIMD_NAME " support - nothing to compare\n");
return 77; /* meson/automake "skipped" */
#else
- printf("evas op table C vs " SIMD_NAME " differential test (seed=%u iterations=%d)\n\n",
+ printf("evas op table " SIMD_REF_NAME " vs " SIMD_NAME " differential test (seed=%u iterations=%d)\n",
seed, iterations);
+ printf("gate mode: " SIMD_MODE_NAME "\n\n");
memset(&total, 0, sizeof(total));
@@ -740,9 +841,10 @@ main(int argc, char **argv)
walk_pt_table("copy_pt", op_copy_pt_funcs, &total);
walk_pt_table("copy_rel_pt", op_copy_rel_pt_funcs, &total);
- printf("\n--- summary ---\n");
+ printf("\n--- summary (gate mode: " SIMD_MODE_NAME ") ---\n");
printf("cases run : %llu\n", total.cases);
printf("differing pixels : %llu\n", total.span_diff);
+ printf(" of which outside the gate's tolerance: %llu\n", total.unexpected_diff);
printf("worst channel delta: %d\n", total.max_delta);
printf(" (non-premultiplied input, informational: %llu diffs, worst delta %d)\n",
total.raw_diff, total.raw_max_delta);
@@ -755,14 +857,42 @@ main(int argc, char **argv)
printf("RESULT: FAIL (buffer overrun or input clobbered)\n");
return 1;
}
+ if (total.unexpected_diff)
+ {
+#ifdef SIMD_REQUIRE_EXACT
+ printf("RESULT: FAIL (%llu differing pixels; this build requires exact "
+ "match against " SIMD_REF_NAME ")\n", total.unexpected_diff);
+#else
+ /* In the tolerant (vs-C) build, the inherited tier is always SSE3:
+ * this code path only runs when NOT SIMD_REQUIRE_EXACT, which means
+ * we're comparing AVX2 against C and tolerating SSE3-inherited diffs. */
+ printf("RESULT: FAIL (%llu differing pixels not inherited from "
+ "sse3 - see classify_diff())\n", total.unexpected_diff);
+#endif
+ return 1;
+ }
+ /* Belt and braces even in tolerant mode: SSE3-inherited divergence is
+ * documented as at most 1 LSB per channel (SSE3's mul4_sym/mul3_sym
+ * rounding gap). If that ever grew past 1, something changed beyond the
+ * documented, accepted divergence, and classify_diff() tolerating it
+ * pixel-for-pixel would hide it from total.unexpected_diff - this catches
+ * it independently. */
if (total.max_delta > 1)
{
- printf("RESULT: FAIL (differences beyond rounding)\n");
+ printf("RESULT: FAIL (worst delta %d exceeds the documented 1-LSB "
+ "divergence bound)\n", total.max_delta);
return 1;
}
if (total.span_diff)
{
- printf("RESULT: PASS with rounding differences (max 1 per channel)\n");
+ /* In the tolerant (vs-C) build, tolerated diffs are inherited from SSE3:
+ * SSE3 also differs from C on those pixels, and AVX2 matches SSE3 exactly
+ * (see classify_diff()). Naming SIMD_REF_NAME (the reference tier) here
+ * would be actively misleading - the divergence is inherited from SSE3,
+ * not from C. This message only appears outside SIMD_REQUIRE_EXACT. */
+ printf("RESULT: PASS (%llu differing pixels, all inherited from sse3 "
+ "(sse3 also differs from c there), max 1 per channel - not a failure)\n",
+ total.span_diff);
return 0;
}
printf("RESULT: PASS (bit exact)\n");
diff --git a/src/tests/evas/meson.build b/src/tests/evas/meson.build
index 41b97736d5..b6c418024b 100644
--- a/src/tests/evas/meson.build
+++ b/src/tests/evas/meson.build
@@ -92,4 +92,33 @@ if cpu_avx2
env : test_env,
timeout : master_timeout
)
+
+ # Same harness, same executable sources, but compared against CPU_SSE3
+ # instead of CPU_C. Some AVX2 kernels are ported operation-for-operation
+ # from an SSE3 original that itself diverges from the plain-C reference
+ # macros (a pre-existing property of the shipped SSE3 code, not something
+ # introduced by the AVX2 port); for those kernels "bit-exact against
+ # SSE3" is the correct acceptance bar, and this target answers that
+ # question directly rather than folding it into the AVX2-vs-C result.
+ #
+ # -DSIMD_REQUIRE_EXACT makes this target's gate reject ANY in-span
+ # difference, of any magnitude - this is the build meant to enforce
+ # bit-exactness, so it must not tolerate the "max 1 LSB is fine" leniency
+ # the vs-C build needs for the known SSE3-inherited divergence. See the
+ # SIMD_REQUIRE_EXACT comment in evas_test_simd_ops.c.
+ evas_avx2_vs_sse3_ops = executable('evas_avx2_vs_sse3_ops',
+ ['evas_test_simd_ops.c',
+ '../../lib/evas/common/evas_op_blend/op_blend_master_avx2.c',
+ '../../lib/evas/common/evas_op_blend/op_blend_master_sse3.c',
+ 'evas_test_simd_avx2_alpha_stub.c'],
+ dependencies: [evas_bin, evas, evas_ext_none_static_deps, eet],
+ c_args : ['-DEVAS_BUILD', '-DSIMD_TIER=CPU_AVX2', '-DSIMD_NAME="avx2"',
+ '-DSIMD_REF_TIER=CPU_SSE3', '-DSIMD_REF_NAME="sse3"',
+ '-DSIMD_REQUIRE_EXACT=1'] + avx2_c_args
+ )
+
+ test('evas-avx2-vs-sse3-ops', evas_avx2_vs_sse3_ops,
+ env : test_env,
+ timeout : master_timeout
+ )
endif
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.