From 233c88120d65bb45a215e833fa3322b03fe2b9b0 Mon Sep 17 00:00:00 2001 From: Marshall Lochbaum Date: Wed, 12 Mar 2025 19:06:59 -0400 Subject: [PATCH 1/2] Singeli function for initial group statistics pass since auto-vectorization isn't great --- build/singeliSubmodule | 2 +- build/src/build.bqn | 2 +- src/builtins/group.c | 35 ++++++++---- src/singeli/src/base.singeli | 5 +- src/singeli/src/group.singeli | 103 ++++++++++++++++++++++++++++++++++ 5 files changed, 134 insertions(+), 13 deletions(-) create mode 100644 src/singeli/src/group.singeli diff --git a/build/singeliSubmodule b/build/singeliSubmodule index 105c9b59..53f42ce4 160000 --- a/build/singeliSubmodule +++ b/build/singeliSubmodule @@ -1 +1 @@ -Subproject commit 105c9b598a4e6eb90347a072042b3064a190bafb +Subproject commit 53f42ce4331176d281fa577408ec5a652bdd9127 diff --git a/build/src/build.bqn b/build/src/build.bqn index 6b32eb23..1dc444be 100644 --- a/build/src/build.bqn +++ b/build/src/build.bqn @@ -742,7 +742,7 @@ cachedBin‿linkerCache ← { "xag"‿"src/builtins/scan.c"‿"scan", "xag"‿"src/builtins/fold.c"‿"fold", "xag"‿"src/builtins/slash.c"‿"slash", "xag"‿"src/builtins/slash.c"‿"replicate", "xag"‿"src/builtins/sort.c"‿"bins", "xa."‿"src/builtins/slash.c"‿"count" - "xag"‿"src/builtins/select.c"‿"select" + "xag"‿"src/builtins/select.c"‿"select", "xag"‿"src/builtins/group.c"‿"group" ⟩ objs ← ⟨⟩ diff --git a/src/builtins/group.c b/src/builtins/group.c index 3c07668c..17f8a8f3 100644 --- a/src/builtins/group.c +++ b/src/builtins/group.c @@ -23,6 +23,11 @@ #include "../utils/calls.h" #include "../utils/mut.h" +#if SINGELI + #define SINGELI_FILE group + #include "../utils/includeSingeli.h" +#endif + extern B ud_c1(B, B); extern B ne_c2(B, B, B); extern B slash_c1(B, B); @@ -57,17 +62,26 @@ static B group_simple(B w, B x, ur xr, usz wia, usz xn, usz* xsh, u8 we) { bool bad = false, sort = true; usz neg = 0, change = 0; void *wp0 = tyany_ptr(w); + #if SINGELI + #define ACCUM(T) \ + u8 bad_t, sort_t; \ + si_group_statistics_##T(wp0, xn, &bad_t, &neg, &sort_t, &change, &max); \ + bad = bad_t; sort = sort_t; + #else + #define ACCUM(T) \ + T prev = -1; \ + for (usz i = 0; i < xn; i++) { \ + T n = ((T*)wp0)[i]; \ + if (n>max) max = n; \ + bad |= n < -1; \ + neg += n == -1; \ + sort &= prev <= n; \ + change += prev != n; \ + prev = n; \ + } + #endif #define CASE(T) case el_##T: { \ - T max = -1, prev = -1; \ - for (usz i = 0; i < xn; i++) { \ - T n = ((T*)wp0)[i]; \ - if (n>max) max = n; \ - bad |= n < -1; \ - neg += n == -1; \ - sort &= prev <= n; \ - change += prev != n; \ - prev = n; \ - } \ + T max = -1; ACCUM(T) \ if (wia>xn) { ria=((T*)wp0)[xn]; bad|=ria<-1; } \ i64 m=(i64)max+1; if (m>ria) ria=m; \ break; } @@ -77,6 +91,7 @@ static B group_simple(B w, B x, ur xr, usz wia, usz xn, usz* xsh, u8 we) { case el_bit: ria = xn? 1+bit_has(wp0,xn,1) : wia? bitp_get(wp0,0) : 0; break; } #undef CASE + #undef ACCUM if (bad) thrM("𝕨⊔𝕩: 𝕨 can't contain elements less than ¯1"); if (ria > (i64)(USZ_MAX)) thrOOM(); diff --git a/src/singeli/src/base.singeli b/src/singeli/src/base.singeli index 06c924fc..1699f5e3 100644 --- a/src/singeli/src/base.singeli +++ b/src/singeli/src/base.singeli @@ -264,7 +264,10 @@ if_inline (hasarch{'X86_64'}) { } else { def { __adds,__subs,__sqrt,vec_broadcast,vec_make, - vec_shift_left_128,vec_shift_right_128,vec_merge_shift_right, + vec_shift_left,vec_shift_left_128, + vec_shift_right,vec_shift_right_128, + vec_merge_shift_left,vec_merge_shift_left_128, + vec_merge_shift_right,vec_merge_shift_right_128, vec_shuffle16_lo } } diff --git a/src/singeli/src/group.singeli b/src/singeli/src/group.singeli new file mode 100644 index 00000000..1ef2ed5b --- /dev/null +++ b/src/singeli/src/group.singeli @@ -0,0 +1,103 @@ +include './base' +include './vecfold' + +if_inline (hasarch{'SSE2'}) { + def fold_addw{v:V} = vfold{+, fold{+, mzip128{v, V**0}}} +} + +def vec_merge_shift_right{a:V=[n]_, b:V, s if hasarch{'SSE2'} and not hasarch{'SSSE3'}} = { + vec_shift_left{a, n-s} | vec_shift_right{b, s} +} +def vec_merge_shift_right{a:V, b:V, 1 if width{V}>128} = { + def nl = width{V}/128 # number of lanes + p:= vec_select{128, tup{a,b}, nl-1+iota{nl}} + vec_merge_shift_right_128{p, b, 1} +} + +def __add{a:(usz), b:(u1)} = a + promote{usz,b} +def __lt{a:V=[_]_, b if knum{b}} = a < V**b +def __eq{a:V=[_]_, b if knum{b}} = a == V**b + +def group_statistics{T} = { + def store{p:(*u8), 0, b:(u1)} = store{p, 0, promote{u8, b}} + + def widen_sum = width{T} <= 8 + def sum_vec = if (widen_sum) fold_addw else vfold{+, .} + + def var{op, get} = { + # Identity, type + def id = match (op) { {(max)} => -1; {(&)} => 1; {_} => 0 } + def S = match (op) { {(max)} => T; {(+)} => usz; {_} => u1 } + + # Scalar accumulator + def updater{v,op}{...a} = { v = op{v, get{...a}} } + def scal{val} = { + v:S = val + tup{v, updater{v, op}} + } + def scal{} = scal{id} + + # Vector accumulator + def vec{l} = { + def V = match (S) { {(T)} => [l]T; {_} => [l]ty_u{T} } + v := V**(if (id==1) maxvalue{ty_u{T}} else id) + def u = updater{v, if (same{op,+}) (-) else op} + def {flush, get} = if (S!=usz) { + def get = match (op) { + {(&)} => all_hom + {(|)} => any_hom + {(max)} => vfold{max, .} + } + tup{{}=>{}, {} => get{v}} + } else { + f:usz = 0 + def flush{} = { f += cast_i{usz, sum_vec{v}}; v = V**0 } + tup{flush, {} => f} + } + tup{u, flush, get} + } + tup{if (S==u1) u8 else S, scal, vec} + } + def {types, init_scal, init_vec} = each{tup, + var{|, {_,w} => w < -1}, # bad + var{+, {_,w} => w == -1}, # neg + var{&, {p,w} => p <= w }, # sort + var{+, {p,w} => p != w }, # change + var{max, {_,w} => w } # max + } + def run{g, ...par} = g{...par} + def runvars{gens, ...par} = each{run{., ...par}, gens} + + fn group_statistics(w0:*void, xn:usz, outs:each{__pnt,types}) : void = { + def {start, init} = if (not has_simd) tup{0, tup{}} else { + def vl = arch_defvw/width{T}; def V = [vl]T + def {accum, flush, get} = flip{runvars{init_vec, vl}} + e:= xn / vl + i:usz = 0 + prev:V = V**(-1) + while (i < e) { + def lmax = 1 << (width{T}-1 - (not widen_sum)*lb{vl}) + l:= min{usz~~lmax, e-i} + @for (w in *V~~w0 + i over l) { + runvars{accum, vec_merge_shift_right{prev, w, 1}, w} + prev = w + } + i+= l + runvars{flush} + } + tup{e*vl, tup{runvars{get}}} + } + def {vals, accum} = flip{each{run, init_scal, ...init}} + prev:T = -1 + if (start > 0) prev = load{*T~~w0, start-1} + @for (w in *T~~w0 over _ from start to xn) { + runvars{accum, prev,w} + prev = w + } + each{store{.,0,.}, outs, vals} + } +} + +export{'si_group_statistics_i8', group_statistics{i8}} +export{'si_group_statistics_i16', group_statistics{i16}} +export{'si_group_statistics_i32', group_statistics{i32}} From 82ebd37b3f2e8095b750cd86e97f6f6d928bb86f Mon Sep 17 00:00:00 2001 From: dzaima Date: Thu, 13 Mar 2025 20:41:46 +0200 Subject: [PATCH 2/2] group statistics test --- src/builtins/internal.c | 18 ++++++++++++++++++ test/cases/build-specific/test_group_stat.bqn | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 test/cases/build-specific/test_group_stat.bqn diff --git a/src/builtins/internal.c b/src/builtins/internal.c index ef5cefd5..05839990 100644 --- a/src/builtins/internal.c +++ b/src/builtins/internal.c @@ -283,7 +283,25 @@ B eequal_c2(B t, B w, B x) { #if TEST_RANGE #include "../utils/calls.h" #endif +#if TEST_GROUP_STAT + extern void (*const si_group_statistics_i8)(void*,usz,uint8_t*,usz*,uint8_t*,usz*,int8_t*); + extern void (*const si_group_statistics_i16)(void*,usz,uint8_t*,usz*,uint8_t*,usz*,int16_t*); + extern void (*const si_group_statistics_i32)(void*,usz,uint8_t*,usz*,uint8_t*,usz*,int32_t*); +#endif B internalTemp_c1(B t, B x) { + #if TEST_GROUP_STAT + u8 bad; usz neg; u8 sort; usz change; i32 max; + #define CASE(T) \ + if (TI(x,elType)==el_##T) { T max_t; si_group_statistics_##T(tyany_ptr(x), IA(x), &bad, &neg, &sort, &change, &max_t); max = max_t; } \ + else + CASE(i8) CASE(i16) CASE(i32) + thrM("bad eltype"); + #undef CASE + decG(x); + f64* rp; B r = m_f64arrv(&rp, 5); + rp[0] = bad; rp[1] = neg; rp[2] = sort; rp[3] = change; rp[4] = max; + return r; + #endif #if TEST_RANGE i64 buf[2]; bool b = getRange_fns[TI(x,elType)](tyany_ptr(x), buf, IA(x)); diff --git a/test/cases/build-specific/test_group_stat.bqn b/test/cases/build-specific/test_group_stat.bqn new file mode 100644 index 00000000..d4f6e69d --- /dev/null +++ b/test/cases/build-specific/test_group_stat.bqn @@ -0,0 +1,19 @@ +# needs: Singeli & -DTEST_GROUP_STAT +"basic test doesn't work; are you on a -DTEST_GROUP_STAT build?" ! 0‿0‿1‿10‿9 ≡ •internal.Temp ↕10 + +%DEF tvar AllEq←{v←⊑𝕩 ⋄ {𝕩≡v?1;!𝕩‿v}¨ ⥊𝕩 ⋄ v} ⋄ _tvar ← {Sel _𝕣 a: AllEq {Sel •internal.Temp 𝕩•internal.Variation a}¨ ∊⟜"Ai8"‿"Ai16"‿"Ai32"⊸/ •internal.ListVariations •internal.Squeeze a} +%USE tvar ⋄ ⊢_tvar ↕10 %% 0‿0‿1‿10‿9 + +%USE tvar ⋄ AllEq {a←𝕩⥊0 ⋄ AllEq {⊑_tvar ¯2⌾(𝕩⊸⊑) a}¨ ↕𝕩}¨ 1+↕40 %% 1 +%USE tvar ⋄ AllEq {a←𝕩⥊0 ⋄ AllEq {⊑_tvar ¯128⌾(𝕩⊸⊑) a}¨ ↕𝕩}¨ 1+↕40 %% 1 + +%USE tvar ⋄ AllEq {⊢_tvar 𝕩⥊0}¨ 1+↕1000 %% 0‿0‿1‿1‿0 +%USE tvar ⋄ AllEq {⊢_tvar 𝕩⥊10}¨ 1+↕1000 %% 0‿0‿1‿1‿10 +%USE tvar ⋄ {! ⟨0,0,0,𝕩,3⟩ ≡ ⊢_tvar 𝕩⥊3‿2}¨ 2+↕1000 +%USE tvar ⋄ {! ⟨0,0,1,𝕩,𝕩-1⟩ ≡ ⊢_tvar ↕𝕩}¨ 1+↕1000 + +%USE tvar ⋄ AllEq {a←𝕩⥊0 ⋄ AllEq {0‿1‿2‿4⊸⊏_tvar 3⌾(𝕩⊸⊑) a}¨ ↕𝕩-1}¨ 2+↕40 %% 0‿0‿0‿3 +%USE tvar ⋄ AllEq {a←𝕩⥊0 ⋄ AllEq { ⊢_tvar 9⌾(𝕩⊸⊑) a}¨ 1+↕𝕩-2}¨ 3+↕40 %% 0‿0‿0‿3‿9 + +%USE tvar ⋄ AllEq {a←𝕩⥊0 ⋄ AllEq {0‿1‿4⊸⊏_tvar ¯1⌾(𝕩⊸⊑) a}¨ ↕𝕩}¨ 2+↕40 %% 0‿1‿0 +%USE tvar ⋄ AllEq {a←𝕩⥊0 ⋄ AllEq {0‿1‿4⊸⊏_tvar 12⌾(𝕩⊸⊑) a}¨ ↕𝕩}¨ 1+↕40 %% 0‿0‿12