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 21f598a43dc800030aac39f4e60e7e9a1a205e24
Author: [email protected] <[email protected]>
AuthorDate: Mon Aug 3 21:09:48 2026 -0600
evas: add a CPU_AVX2 tier and parameterize the SIMD differential test
Lands the whole dispatch and build path while every AVX2 slot is still
NULL, so rendering is provably unchanged and the plumbing can be
reviewed on its own. -mavx2 goes on a dedicated static library rather
than on native_arch_opt_c_args, which covers the SSE3 kernels too -
those run on any SSE3 cpu, so AVX2 emitted into them would fault on
hardware without it. The span selectors already return the first
non-NULL slot walking down from the highest tier, so kernels can now
be added one group at a time with every intermediate commit shippable.
The static_library initially used evas_deps (which includes the draw
declare_dependency's sources:), causing meson to recompile seven
unrelated production files (draw_main.c, draw_main_neon.c,
draw_convert.c, draw_alpha_main.c, etc2_encoder.c, rg_etc1.c,
rg_etc2.c) with -mavx2, none of which have runtime AVX2 dispatch
guards. This was latent (libevas_opt.a's -msse3 copies always won link
resolution) but depended on link order rather than design, so it is
fixed here rather than left for a later commit to expose: the
dependency list is built via partial_dependency(compile_args: true,
includes: true) so only header/compile-flag information is carried
into evas_opt_avx2, not sources. The resulting static library contains
exactly one object, op_blend_master_avx2.c.o.
The differential-test harness - buffer guards, contract-aware
generators, length and offset sweeps, out-of-span and source-clobber
detection - is entirely tier-agnostic. Rather than copy it for AVX2,
the tier is taken as a build macro and the file compiled once per
tier; NEON behaviour is unchanged but untested here since NEON is not
compiled on this x86 host.
Two bugs surfaced in review before the guard was trustworthy:
- The tier-availability #if guard was placed before the includes that
define CPU_NEON and CPU_AVX2 (in evas_blend_ops.h, pulled in via
evas_common_private.h). In a preprocessor #if, undefined identifiers
evaluate to 0, so SIMD_TIER and CPU_NEON both evaluated to 0, making
(0 == 0) always true and SIMD_TIER_UNAVAILABLE defined
unconditionally - every build of this file silently skipped its own
test. Moving the guard to after the include fixed it.
- Separately, the skip check hardcoded a BUILD_NEON test regardless of
tier, so an AVX2 build would report "skipped" (via the NEON
condition, which is false on x86) without ever actually exercising
AVX2. Each tier now only skips when its own BUILD flag is
unavailable, and the skip message names the tier via SIMD_NAME
instead of hardcoding "BUILD_NEON".
Verified: both tiers syntax-check correctly and build clean; the
AVX2-tier skip branch is not compiled while the NEON-tier skip branch
is (grep-verified), and evas_opt_avx2.a contains exactly the one
expected object.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
meson.build | 6 ++
.../common/evas_op_blend/op_blend_master_avx2.c | 36 ++++++++++++
src/lib/evas/common/evas_op_blend_main_.c | 28 +++++++++
src/lib/evas/common/meson.build | 6 ++
src/lib/evas/include/evas_blend_ops.h | 4 +-
src/lib/evas/meson.build | 18 ++++++
.../{evas_test_neon_ops.c => evas_test_simd_ops.c} | 68 ++++++++++++++--------
src/tests/evas/meson.build | 4 +-
8 files changed, 142 insertions(+), 28 deletions(-)
diff --git a/meson.build b/meson.build
index ee4d96eca1..7771923676 100644
--- a/meson.build
+++ b/meson.build
@@ -168,6 +168,8 @@ if cc.compiles(code, name: 'funcptr(...) works')
endif
cpu_sse3 = false
+cpu_avx2 = false
+avx2_c_args = [ ]
cpu_neon = false
cpu_neon_intrinsics = false
native_arch_opt_c_args = [ ]
@@ -183,6 +185,10 @@ if get_option('native-arch-optimization')
config_h.set10('BUILD_SSE3', true)
native_arch_opt_c_args = [ '-msse3' ]
message('x86 build - MMX + SSE3 enabled')
+ cpu_avx2 = true
+ config_h.set10('BUILD_AVX2', true)
+ avx2_c_args = [ '-mavx2' ]
+ message('x86 build - AVX2 enabled')
elif host_machine.cpu_family() == 'arm'
cpu_neon = true
config_h.set10('BUILD_NEON', true)
diff --git a/src/lib/evas/common/evas_op_blend/op_blend_master_avx2.c b/src/lib/evas/common/evas_op_blend/op_blend_master_avx2.c
new file mode 100644
index 0000000000..a9d734179f
--- /dev/null
+++ b/src/lib/evas/common/evas_op_blend/op_blend_master_avx2.c
@@ -0,0 +1,36 @@
+/* AVX2 blend kernels.
+ *
+ * This translation unit is the only one in evas compiled with -mavx2, and it
+ * is a separate static library for that reason: the SSE3 kernels next door run
+ * on any SSE3 cpu, so letting the compiler emit AVX2 into them would fault on
+ * hardware that has no AVX2. Whether the code in *this* file runs at all is
+ * decided at runtime by CPU_FEATURE_AVX2.
+ */
+
+#define NEED_AVX2 1
+
+#include "Eina.h"
+#include "Evas.h"
+#include "evas_common_types.h"
+
+EXPORTAPI void evas_common_cpu_end_opt(void);
+
+#include "config.h"
+#include "evas_blend_ops.h"
+
+extern RGBA_Gfx_Func op_blend_span_funcs[SP_LAST][SM_LAST][SC_LAST][DP_LAST][CPU_LAST];
+extern RGBA_Gfx_Func op_blend_rel_span_funcs[SP_LAST][SM_LAST][SC_LAST][DP_LAST][CPU_LAST];
+
+void
+evas_common_op_blend_init_avx2(void)
+{
+#ifdef BUILD_AVX2
+#endif
+}
+
+void
+evas_common_op_blend_rel_init_avx2(void)
+{
+#ifdef BUILD_AVX2
+#endif
+}
diff --git a/src/lib/evas/common/evas_op_blend_main_.c b/src/lib/evas/common/evas_op_blend_main_.c
index c43daf787f..698a2ab4dc 100644
--- a/src/lib/evas/common/evas_op_blend_main_.c
+++ b/src/lib/evas/common/evas_op_blend_main_.c
@@ -94,12 +94,20 @@ evas_common_gfx_compositor_blend_rel_get(void)
#ifdef BUILD_SSE3
void evas_common_op_blend_init_sse3(void);
#endif
+#ifdef BUILD_AVX2
+void evas_common_op_blend_init_avx2(void);
+void evas_common_op_blend_rel_init_avx2(void);
+#endif
static void
op_blend_init(void)
{
memset(op_blend_span_funcs, 0, sizeof(op_blend_span_funcs));
memset(op_blend_pt_funcs, 0, sizeof(op_blend_pt_funcs));
+#ifdef BUILD_AVX2
+ if (evas_common_cpu_has_feature(CPU_FEATURE_AVX2))
+ evas_common_op_blend_init_avx2();
+#endif
#ifdef BUILD_SSE3
if (evas_common_cpu_has_feature(CPU_FEATURE_SSE3))
evas_common_op_blend_init_sse3();
@@ -159,6 +167,14 @@ blend_gfx_span_func_cpu(int s, int m, int c, int d)
{
RGBA_Gfx_Func func = NULL;
int cpu = CPU_N;
+#ifdef BUILD_AVX2
+ if (evas_common_cpu_has_feature(CPU_FEATURE_AVX2))
+ {
+ cpu = CPU_AVX2;
+ func = op_blend_span_funcs[s][m][c][d][cpu];
+ if (func) return func;
+ }
+#endif
#ifdef BUILD_SSE3
if (evas_common_cpu_has_feature(CPU_FEATURE_SSE3))
{
@@ -390,6 +406,10 @@ op_blend_rel_init(void)
{
memset(op_blend_rel_span_funcs, 0, sizeof(op_blend_rel_span_funcs));
memset(op_blend_rel_pt_funcs, 0, sizeof(op_blend_rel_pt_funcs));
+#ifdef BUILD_AVX2
+ if (evas_common_cpu_has_feature(CPU_FEATURE_AVX2))
+ evas_common_op_blend_rel_init_avx2();
+#endif
#ifdef BUILD_SSE3
evas_common_op_blend_rel_init_sse3();
#endif
@@ -442,6 +462,14 @@ blend_rel_gfx_span_func_cpu(int s, int m, int c, int d)
{
RGBA_Gfx_Func func = NULL;
int cpu = CPU_N;
+#ifdef BUILD_AVX2
+ if (evas_common_cpu_has_feature(CPU_FEATURE_AVX2))
+ {
+ cpu = CPU_AVX2;
+ func = op_blend_rel_span_funcs[s][m][c][d][cpu];
+ if (func) return func;
+ }
+#endif
#ifdef BUILD_SSE3
if (evas_common_cpu_has_feature(CPU_FEATURE_SSE3))
{
diff --git a/src/lib/evas/common/meson.build b/src/lib/evas/common/meson.build
index e2df18bf06..32807cea49 100644
--- a/src/lib/evas/common/meson.build
+++ b/src/lib/evas/common/meson.build
@@ -88,6 +88,12 @@ if cpu_sse3 == true
])
endif
+if cpu_avx2 == true
+ evas_src_opt_avx2 += files([
+ 'evas_op_blend/op_blend_master_avx2.c'
+ ])
+endif
+
if cpu_neon == true and cpu_neon_intrinsics == false
evas_src_opt += files([
'evas_op_copy/op_copy_neon.S'
diff --git a/src/lib/evas/include/evas_blend_ops.h b/src/lib/evas/include/evas_blend_ops.h
index 45cd3744cd..82ff0fccd1 100644
--- a/src/lib/evas/include/evas_blend_ops.h
+++ b/src/lib/evas/include/evas_blend_ops.h
@@ -77,8 +77,10 @@
#define CPU_NEON 5
/* CPU SSE3 */
#define CPU_SSE3 6
+/* CPU AVX2 */
+#define CPU_AVX2 7
/* cpu flags count */
-#define CPU_LAST 7
+#define CPU_LAST 8
/* some useful constants */
diff --git a/src/lib/evas/meson.build b/src/lib/evas/meson.build
index 7b193c0109..4c2a5f8ff2 100644
--- a/src/lib/evas/meson.build
+++ b/src/lib/evas/meson.build
@@ -154,6 +154,7 @@ evas_src += files([
])
evas_src_opt = [ ]
+evas_src_opt_avx2 = [ ]
evas_ext_none_static_deps += dependency('freetype2')
@@ -254,6 +255,23 @@ if cpu_sse3 == true or cpu_neon == true and cpu_neon_intrinsics == false
evas_link += [ evas_opt ]
endif
+if cpu_avx2 == true
+ evas_opt_avx2_partial_deps = [ ]
+ foreach dep : [eina, eo, ector, emile] + evas_deps + evas_ext_none_static_deps
+ evas_opt_avx2_partial_deps += dep.partial_dependency(compile_args: true, includes: true)
+ endforeach
+ evas_opt_avx2 = static_library('evas_opt_avx2',
+ sources: [evas_src_opt_avx2, pub_eo_file_target, priv_eo_file_target],
+ include_directories:
+ [ include_directories('../../..') ] +
+ evas_include_directories +
+ [vg_common_inc_dir],
+ c_args: avx2_c_args,
+ dependencies: evas_opt_avx2_partial_deps,
+ )
+ evas_link += [ evas_opt_avx2 ]
+endif
+
foreach loader_inst : evas_image_loaders_file
loader = loader_inst[0]
loader_type = loader_inst[1]
diff --git a/src/tests/evas/evas_test_neon_ops.c b/src/tests/evas/evas_test_simd_ops.c
similarity index 91%
rename from src/tests/evas/evas_test_neon_ops.c
rename to src/tests/evas/evas_test_simd_ops.c
index d1dad9d920..9336679cc0 100644
--- a/src/tests/evas/evas_test_neon_ops.c
+++ b/src/tests/evas/evas_test_simd_ops.c
@@ -1,6 +1,6 @@
/* Differential test: C reference vs NEON kernels in the evas span/point op tables.
*
- * The op tables are indexed by CPU, so after init both [CPU_C] and [CPU_NEON]
+ * The op tables are indexed by CPU, so after init both [CPU_C] and [SIMD_TIER]
* slots are live in the same process. We can therefore run the two variants
* over identical buffers and compare, with no second build and no mocking.
*
@@ -12,6 +12,15 @@
# include "config.h"
#endif
+/* Which vector tier this build compares against the C reference. The build
+ * compiles this file once per tier; everything below is tier-agnostic. */
+#ifndef SIMD_TIER
+# error "SIMD_TIER must be defined by the build (e.g. -DSIMD_TIER=CPU_AVX2)"
+#endif
+#ifndef SIMD_NAME
+# error "SIMD_NAME must be defined by the build (e.g. -DSIMD_NAME=\"avx2\")"
+#endif
+
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -20,6 +29,15 @@
#include "evas_common_private.h"
#include "evas_blend_private.h"
+/* Check if the current tier has a corresponding BUILD flag. Each tier can only
+ * run if its implementation is compiled in. This must come AFTER the includes
+ * above, which pull in evas_blend_ops.h and define CPU_NEON, CPU_AVX2, etc. */
+#if (SIMD_TIER == CPU_NEON) && !defined(BUILD_NEON)
+# define SIMD_TIER_UNAVAILABLE 1
+#elif (SIMD_TIER == CPU_AVX2) && !defined(BUILD_AVX2)
+# define SIMD_TIER_UNAVAILABLE 1
+#endif
+
/* The op TUs only need these symbols from the rest of evas. Force NEON on so
* both the C and the NEON init paths populate their slots regardless of any
* EVAS_NEON_DISABLE setting in the environment. */
@@ -243,7 +261,7 @@ typedef struct
unsigned long long cases;
unsigned long long span_diff; /* differing pixels inside the span */
unsigned long long raw_diff; /* ditto, but from invalid PAT_RAW input */
- unsigned long long oob_neon; /* NEON wrote outside its span */
+ unsigned long long oob_simd; /* NEON wrote outside its span */
unsigned long long oob_c; /* C wrote outside its span */
unsigned long long src_clobber; /* an implementation modified src/mask */
int max_delta; /* worst per-channel difference */
@@ -287,7 +305,7 @@ report(Category cat, const char *table, int s, int m, int c, int d, int len,
printf(" %s[%s][%s][%s][%s] len=%d off=(s%d,d%d,m%d) pat=%s: %s\n",
table, sp_names[s], sm_names[m], sc_names[c], dp_names[d],
len, off[0], off[1], off[2], pat_names[pat], what);
- printf(" pixel %d: neon=%08x c=%08x (src="" mask=%02x col=%08x)\n",
+ printf(" pixel %d: " SIMD_NAME "=%08x c=%08x (src="" mask=%02x col=%08x)\n",
idx, got, want, sv, mv, col);
if (reported[cat] == max_report)
printf(" ... further reports of this kind suppressed (--max-report to raise)\n");
@@ -351,7 +369,7 @@ run_span_case(const char *table, RGBA_Gfx_Func fc, RGBA_Gfx_Func fn,
memcmp(msk.raw, msk_o.raw, BUF_BYTES))
{
report(CAT_CLOBBER, table, s, m, c, d, len, off, pat,
- "NEON modified its source or mask", 0, 0, 0, 0, 0, col);
+ SIMD_NAME " modified its source or mask", 0, 0, 0, 0, 0, col);
st->src_clobber++;
memcpy(src.raw, src_o.raw, BUF_BYTES);
memcpy(msk.raw, msk_o.raw, BUF_BYTES);
@@ -369,9 +387,9 @@ run_span_case(const char *table, RGBA_Gfx_Func fc, RGBA_Gfx_Func fn,
if (((DATA32 *)dst_n.raw)[i] != orig)
{
report(CAT_OOB, table, s, m, c, d, len, off, pat,
- "NEON wrote outside its span", i - PAD_PIX - doff,
+ SIMD_NAME " wrote outside its span", i - PAD_PIX - doff,
((DATA32 *)dst_n.raw)[i], orig, 0, 0, col);
- st->oob_neon++;
+ st->oob_simd++;
}
if (((DATA32 *)dst_c.raw)[i] != orig)
{
@@ -422,7 +440,7 @@ walk_span_table(const char *table,
int iterations, Stats *total)
{
int s, m, c, d, li, oi, p, it;
- int pairs = 0, neon_only = 0;
+ int pairs = 0, simd_only = 0;
for (s = 0; s < SP_LAST; s++)
for (m = 0; m < SM_LAST; m++)
@@ -430,15 +448,15 @@ walk_span_table(const char *table,
for (d = 0; d < DP_LAST; d++)
{
RGBA_Gfx_Func fc = t[s][m][c][d][CPU_C];
- RGBA_Gfx_Func fn = t[s][m][c][d][CPU_NEON];
+ RGBA_Gfx_Func fn = t[s][m][c][d][SIMD_TIER];
Stats st;
if (!fn) continue;
if (!fc)
{
- printf(" %s[%s][%s][%s][%s]: NEON slot with no C reference\n",
+ printf(" %s[%s][%s][%s][%s]: " SIMD_NAME " slot with no C reference\n",
table, sp_names[s], sm_names[m], sc_names[c], dp_names[d]);
- neon_only++;
+ simd_only++;
continue;
}
pairs++;
@@ -456,15 +474,15 @@ walk_span_table(const char *table,
total->raw_diff += st.raw_diff;
if (st.raw_max_delta > total->raw_max_delta)
total->raw_max_delta = st.raw_max_delta;
- total->oob_neon += st.oob_neon;
+ total->oob_simd += st.oob_simd;
total->oob_c += st.oob_c;
total->src_clobber += st.src_clobber;
if (st.max_delta > total->max_delta) total->max_delta = st.max_delta;
- if (st.span_diff || st.oob_neon || st.oob_c || st.src_clobber)
- printf(" %-10s[%-5s][%-5s][%-5s][%-5s] diff=%llu oob_neon=%llu oob_c=%llu clobber=%llu maxdelta=%d\n",
+ 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",
table, sp_names[s], sm_names[m], sc_names[c], dp_names[d],
- st.span_diff, st.oob_neon, st.oob_c, st.src_clobber,
+ 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
* scale interpolation really do emit transparent-but-coloured
@@ -475,8 +493,8 @@ walk_span_table(const char *table,
st.raw_diff, st.raw_max_delta);
}
- printf("%s: %d C/NEON pairs compared", table, pairs);
- if (neon_only) printf(", %d NEON-only slots", neon_only);
+ printf("%s: %d C/" SIMD_NAME " pairs compared", table, pairs);
+ if (simd_only) printf(", %d " SIMD_NAME "-only slots", simd_only);
printf("\n");
}
@@ -496,7 +514,7 @@ walk_pt_table(const char *table,
for (d = 0; d < DP_LAST; d++)
{
RGBA_Gfx_Pt_Func fc = t[s][m][c][d][CPU_C];
- RGBA_Gfx_Pt_Func fn = t[s][m][c][d][CPU_NEON];
+ RGBA_Gfx_Pt_Func fn = t[s][m][c][d][SIMD_TIER];
unsigned long long diff = 0;
int maxd = 0;
@@ -549,7 +567,7 @@ walk_pt_table(const char *table,
diff, maxd);
}
- printf("%s: %d C/NEON pairs compared\n", table, pairs);
+ printf("%s: %d C/" SIMD_NAME " pairs compared\n", table, pairs);
}
/*----------------------------------------------------------------------------
@@ -630,7 +648,7 @@ bench_span_table(const char *table,
for (d = 0; d < DP_LAST; d++)
{
RGBA_Gfx_Func fc = t[s][m][c][d][CPU_C];
- RGBA_Gfx_Func fn = t[s][m][c][d][CPU_NEON];
+ RGBA_Gfx_Func fn = t[s][m][c][d][SIMD_TIER];
double tc, tn;
DATA32 col;
@@ -649,7 +667,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 NEON %7.1f Mpx/s %5.2fx\n",
+ printf(" %-10s[%-5s][%-5s][%-5s][%-5s] C %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);
}
@@ -687,11 +705,11 @@ main(int argc, char **argv)
if (!seed) seed = 1;
rng_state = seed;
-#ifndef BUILD_NEON
- printf("built without BUILD_NEON - nothing to compare\n");
+#ifdef SIMD_TIER_UNAVAILABLE
+ printf("built without " SIMD_NAME " support - nothing to compare\n");
return 77; /* meson/automake "skipped" */
#else
- printf("evas op table C vs NEON differential test (seed=%u iterations=%d)\n\n",
+ printf("evas op table C vs " SIMD_NAME " differential test (seed=%u iterations=%d)\n\n",
seed, iterations);
memset(&total, 0, sizeof(total));
@@ -728,11 +746,11 @@ main(int argc, char **argv)
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);
- printf("NEON out-of-span : %llu\n", total.oob_neon);
+ printf(SIMD_NAME " out-of-span : %llu\n", total.oob_simd);
printf("C out-of-span : %llu\n", total.oob_c);
printf("input clobbered : %llu\n", total.src_clobber);
- if (total.oob_neon || total.oob_c || total.src_clobber)
+ if (total.oob_simd || total.oob_c || total.src_clobber)
{
printf("RESULT: FAIL (buffer overrun or input clobbered)\n");
return 1;
diff --git a/src/tests/evas/meson.build b/src/tests/evas/meson.build
index 112b291145..55fd1fd86e 100644
--- a/src/tests/evas/meson.build
+++ b/src/tests/evas/meson.build
@@ -45,9 +45,9 @@ test('evas-suite', evas_suite,
# on builds without BUILD_NEON.
if cpu_neon
evas_neon_ops = executable('evas_neon_ops',
- ['evas_test_neon_ops.c'],
+ ['evas_test_simd_ops.c'],
dependencies: [evas_bin, evas, evas_ext_none_static_deps, eet], #internal headers, as above
- c_args : ['-DEVAS_BUILD']
+ c_args : ['-DEVAS_BUILD', '-DSIMD_TIER=CPU_NEON', '-DSIMD_NAME="neon"']
)
test('evas-neon-ops', evas_neon_ops,
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.