This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch overline-textgrid
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.

Reply via email to