singeli num_squeeze

This commit is contained in:
dzaima 2022-06-29 23:25:11 +03:00
parent e31c574fd8
commit 75bfa5d905
11 changed files with 272 additions and 36 deletions

View File

@ -216,7 +216,7 @@ preSingeliBin:
@${MAKE} i_singeli=0 singeli=0 force_build_dir=obj/presingeli f= lf= postmsg="singeli sources:" i_t=presingeli i_f='-O1 -DPRE_SINGELI' FFI=0 OUTPUT=obj/presingeli/BQN c
build_singeli: ${addprefix src/singeli/gen/, cmp.c dyarith.c copy.c equal.c scan.c slash.c}
build_singeli: ${addprefix src/singeli/gen/, cmp.c dyarith.c copy.c equal.c squeeze.c scan.c slash.c}
@echo $(postmsg)
src/singeli/gen/%.c: src/singeli/src/%.singeli preSingeliBin
@echo $< | cut -c 17- | sed 's/^/ /'

View File

@ -1,5 +1,14 @@
#include "../core.h"
// #undef SINGELI
#if SINGELI
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#include "../singeli/gen/squeeze.c"
#pragma GCC diagnostic pop
#endif
FORCE_INLINE B num_squeeze_choose(B x, u32 or) {
if (or==0) goto r_bit;
else if (or<=(u32)I8_MAX ) goto r_i8;
@ -30,44 +39,61 @@ B num_squeeze(B x) {
usz ia = a(x)->ia;
u8 xe = TI(x,elType);
#if !SINGELI
usz i = 0;
#endif
u32 or = 0; // using bitwise or as an approximate ⌈´
switch (xe) { default: UD;
case el_bit: goto r_x;
case el_i8: { i8* xp = i8any_ptr (x); for (; i < ia; i++) { i32 c = xp[i]; or|= (u8)c; } goto r_orI8; }
case el_i16: { i16* xp = i16any_ptr(x); for (; i < ia; i++) { i32 c = xp[i]; or|= ((u32)c & ~1) ^ (u32)(c>>31); } goto r_or; }
case el_i32: { i32* xp = i32any_ptr(x); for (; i < ia; i++) { i32 c = xp[i]; or|= ((u32)c & ~1) ^ (u32)(c>>31); } goto r_or; }
case el_f64: {
f64* xp = f64any_ptr(x);
for (; i < ia; i++) {
f64 cf = xp[i];
i32 c = (i32)cf;
if (c!=cf) goto r_x; // already f64
or|= ((u32)c & ~1) ^ (u32)(c>>31);
#if SINGELI
case el_i8: { or = avx2_squeeze_i8 ((u8*)i8any_ptr (x), ia); goto r_orI8; }
case el_i16: { or = avx2_squeeze_i16((u8*)i16any_ptr(x), ia); goto r_or; }
case el_i32: { or = avx2_squeeze_i32((u8*)i32any_ptr(x), ia); goto r_or; }
case el_f64: { or = avx2_squeeze_f64((u8*)f64any_ptr(x), ia); goto r_orF64; }
#else
case el_i8: { i8* xp = i8any_ptr (x); for (; i < ia; i++) { i32 c = xp[i]; or|= (u8)c; } goto r_orI8; }
case el_i16: { i16* xp = i16any_ptr(x); for (; i < ia; i++) { i32 c = xp[i]; or|= ((u32)c & ~1) ^ (u32)(c>>31); } goto r_or; }
case el_i32: { i32* xp = i32any_ptr(x); for (; i < ia; i++) { i32 c = xp[i]; or|= ((u32)c & ~1) ^ (u32)(c>>31); } goto r_or; }
case el_f64: {
f64* xp = f64any_ptr(x);
for (; i < ia; i++) {
f64 cf = xp[i];
i32 c = (i32)cf;
if (c!=cf) goto r_x; // already f64
or|= ((u32)c & ~1) ^ (u32)(c>>31);
}
goto r_or;
}
goto r_or;
}
#endif
case el_B: case el_c8: case el_c16: case el_c32:; /*fallthrough*/
}
B* xp = arr_bptr(x);
if (xp==NULL) return num_squeezeF(x, ia);
for (; i < ia; i++) {
if (RARE(!q_i32(xp[i]))) {
while (i<ia) if (!isF64(xp[i++])) goto r_x;
goto r_f64;
#if SINGELI
or = avx2_squeeze_B((u8*)xp, ia);
if (or==0xfffffffe) goto r_f64;
goto r_orF64;
r_orF64: if (or==0xffffffff) goto r_x; else goto r_or;
#else
for (; i < ia; i++) {
if (RARE(!q_i32(xp[i]))) {
while (i<ia) if (!isF64(xp[i++])) goto r_x;
goto r_f64;
}
i32 c = o2iu(xp[i]);
or|= ((u32)c & ~1) ^ (u32)(c>>31);
}
i32 c = o2iu(xp[i]);
or|= ((u32)c & ~1) ^ (u32)(c>>31);
}
goto r_or;
goto r_or;
#endif
r_or : return num_squeeze_choose(x, or);
r_x : return FL_SET(x, fl_squoze);
r_f64 : return FL_SET(toF64Any(x), fl_squoze);
r_orI8 : if (or>=2) goto r_x; else goto r_i8;
r_i8 : return FL_SET(toI8Any (x), fl_squoze);
r_orI8 : if (or>=2) goto r_x; else goto r_bit;
r_bit : return FL_SET(taga(toBitArr(x)), fl_squoze);
}
B chr_squeeze(B x) {
usz ia = a(x)->ia;

View File

@ -60,10 +60,10 @@ def iota{T & w256{T,32}} = make{T,0,1,2,3,4,5,6,7}
def iota{T & w256{T,16}} = make{T,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}
def iota{T & w256{T,8}} = make{T,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31}
# bit arith
def __xor{a:T, b:T & w256i{T}} = T ~~ emit{[8]f32, '_mm256_xor_ps', v2f{a}, v2f{b}}
def __and{a:T, b:T & w256i{T}} = T ~~ emit{[8]f32, '_mm256_and_ps', v2f{a}, v2f{b}}
def __or {a:T, b:T & w256i{T}} = T ~~ emit{[8]f32, '_mm256_or_ps', v2f{a}, v2f{b}}
def __not{a:T & w256u{T}} = a ^ broadcast{T, ~cast{eltype{T},0}}
# float comparison

View File

@ -125,8 +125,8 @@ def shuf{I==[4]u32, x:T, n & w256{T} & knum{n}} = T ~~ emit{[8]i32, '_mm256_shuf
def shuf{I==[4]u64, x:T, n & w256{T} & knum{n}} = T ~~ emit{[4]f64, '_mm256_permute4x64_pd', v2d{x}, n}
def shufHalves{x:T, y:T, n & w256{T} & knum{n}} = T ~~ emit{[4]i64, '_mm256_permute2x128_si256', v2i{x}, v2i{y}, n}
def sel{I, x:T, i:[8]i32 & w256{I,32}} = T ~~ emit{[32]u8, '_mm256_permutevar8x32_epi32', v2i{x}, i}
def sel{I, x:T, i:[32]i8 & w128{I, 8}} = T ~~ emit{[32]u8, '_mm256_shuffle_epi8', v2i{x}, i}
def sel{I, x:T, i:[8]i32 & w256{T} & w256{I,32}} = T ~~ emit{[32]u8, '_mm256_permutevar8x32_epi32', v2i{x}, i}
def sel{I, x:T, i:[32]i8 & w256{T} & w128{I, 8}} = T ~~ emit{[32]u8, '_mm256_shuffle_epi8', v2i{x}, i}
def extract{x:T, i & w256i{T,8 } & knum{i}} = emit{eltype{T}, '_mm256_extract_epi8', x, i}
def extract{x:T, i & w256i{T,16} & knum{i}} = emit{eltype{T}, '_mm256_extract_epi16', x, i}
@ -183,3 +183,7 @@ def ucvt{T, x:X & w256u{X,64} & T==u16} = to_el{T, sel{[16]i8, ucvt{u32,x}, make
def ucvt{T, x:X & w256u{X,64} & T== u8} = to_el{T, sel{[16]i8, ucvt{u32,x}, make{[32]i8, 4*iota{32}}}}
def ucvt{T, x:X & w256{X} & width{T}==width{eltype{X}}} = to_el{T, x} # TODO check for not being f64/i64
def cvt2{T, x:X & T==i32 & X==[4]f64} = emit{[4]i32, '_mm256_cvtpd_epi32', x}
def cvt2{T, x:X & T==f64 & X==[4]i32} = emit{[4]f64, '_mm256_cvtepi32_pd', x}

View File

@ -102,9 +102,10 @@ def forNZ{vars,begin,end,block} = {
}
}
def maxvalue{T & T==u8 } = 0xff
def maxvalue{T & T==u16} = 0xffff
def maxvalue{T & T==u32} = 0xffffffff
def minvalue{T & isunsigned{T}} = 0
def maxvalue{T & isunsigned{T}} = (1<<width{T})-1
def minvalue{T & issigned{T}} = - (1<<(width{T}-1))
def maxvalue{T & issigned{T}} = (1<<(width{T}-1))-1
# tuple operations
def broadcast{T, v & isprim{T}} = v

View File

@ -49,10 +49,16 @@ def maskAfter{n} = {
# store low packed elements of x to P
def storeLow{ptr:P, w, x:T & width{T}==w} = store{*T~~ptr, 0, x}
def storeLow{ptr:P, w, x:T & w128{T} & w==16} = emit{void, '_mm_storeu_si16', ptr, v2i{x}}
def storeLow{ptr:P, w, x:T & w128{T} & w==32} = emit{void, '_mm_storeu_si32', ptr, v2i{x}}
def storeLow{ptr:P, w, x:T & w128{T} & w==64} = emit{void, '_mm_storeu_si64', ptr, v2i{x}}
def storeLow{ptr:P, w, x:T & w256{T} & w==16} = storeLow{ptr, w, half{x, 0}}
def storeLow{ptr:P, w, x:T & w256{T} & w==32} = storeLow{ptr, w, half{x, 0}}
def storeLow{ptr:P, w, x:T & w256{T} & w==64} = storeLow{ptr, w, half{x, 0}}
def storeLow{ptr:P, w, x:T & w256{T} & w==128} = store{*[128/width{eltype{T}}](eltype{T})~~ptr, 0, half{x, 0}}
def storeLow{ptr:P, w, x:T & w256{T} & w==64} = emit{void, '_mm_storeu_si64', ptr, v2i{half{x, 0}}}
def storeLow{ptr:P, w, x:T & w256{T} & w==32} = emit{void, '_mm_storeu_si32', ptr, v2i{half{x, 0}}}
def storeLow{ptr:P, w, x:T & w256{T} & w==16} = emit{void, '_mm_storeu_si16', ptr, v2i{half{x, 0}}}
def loadLow{ptr:P, w & w128{eltype{P}} & w==128} = eltype{P} ~~ load{*[16]u8 ~~ ptr}
def loadLow{ptr:P, w & w128{eltype{P}} & w== 64} = eltype{P} ~~ emit{[16]u8, '_mm_loadu_si64', ptr}
@ -82,10 +88,14 @@ def loadBatch{ptr:P, n, T} = {
cvt{E0, T, loadLow{*to_el{E0, T} ~~ rpos, vcount{T}*width{E0}}}
}
def maskedLoop{bulk, l, step} = { # TODO version assuming l!=0
def maskedLoop{bulk, l, step} = maskedLoop{bulk, 0, l, step}
def maskedLoop{bulk, i0, l, step} = {
m:u64 = l/bulk
@for (i to m) step{i, maskNone}
@for (i from i0 to m) step{i, maskNone}
left:= l & (bulk-1)
if (left!=0) step{m, maskAfter{left}}
}
}

View File

@ -0,0 +1,90 @@
include './base'
include './sse3'
include './avx'
include './avx2'
include './mask'
include 'util/tup'
def preserve_negative_zero = 0
def inRangeLen{x:T, start, count & issigned{eltype{T}}} = {
def TU = ty_u{T}
(TU~~(x-broadcast{T,start})) < broadcast{TU,count}
}
def inRangeLen{x:T, start, count & isunsigned{eltype{T}}} = {
def TS = ty_s{T}
def h = 1 << (width{eltype{T}}-1)
(TS~~x)-broadcast{TS,start-h} < broadcast{TS,count-h}
}
def inRangeIncl{x:T, start, end} = inRangeLen{x, start, end-start+1}
def inRangeExcl{x:T, start, end} = inRangeLen{x, start, end-start}
def isSNaN{x:T & isunsigned{eltype{T}}} = inRangeLen{x<<1, (0xFFE<<52)+2, (1<<52)-2}
def fold{F, x:T} = {
show{'WARNING: using fallback fold'}
def E = eltype{T}
r:E = 0
each{{i} => { r = F{r, cast_i{E, extract{x, i}}, iota{vcount{T}}} }}
promote{u32, r}
}
def fold{F, x:T & w128{T}} = {
c:= x
def EW = width{eltype{T}}
if (EW<=64) c = F{c, shuf{[4]u32, c, 4b1032}}
if (EW<=32) c = F{c, shuf{[4]u32, c, 4b2301}}
if (EW<=16) c = F{c, sel{[16]u8, c, make{[16]i8, iota{16}^2}}}
if (EW<=8) c = F{c, sel{[16]u8, c, make{[16]i8, iota{16}^1}}}
extract{c, 0}
}
def fold{F, x:T & w256{T}} = fold{F, F{half{x, 0}, half{x, 1}}}
squeeze{vw, X0}(x0:*u8, len:Size) : u32 = {
def X = tern{X0==u64, f64, X0}
def bulk = vw / width{X}
def XV = [bulk]X
def E = tern{X==f64, u32, ty_u{X}}
def EV = [bulk]E
xp:= *X~~x0
r1:= broadcast{EV, 0}
if (X==i8) {
maskedLoop{bulk, len, {i, M} => {
v0:= loadBatch{xp, i, XV}
if (anyne{broadcast{EV, 0xfe} & EV~~v0, broadcast{EV, 0}, M}) return{2}
}}
0
} else {
maskedLoop{bulk, len, {i, M} => {
v0:= loadBatch{xp, i, XV}
def toint{x:T & isint{eltype{T}}} = x
def toint{flt:T & X==f64} = {
int:= cvt2{i32, flt}
def conv{x} = tern{preserve_negative_zero, ty_u{x}, x}
if (anyne{conv{flt}, conv{cvt2{f64, int}}, M}) { # is any not an integer
if (X0==f64) return{0xffff_ffff}
maskedLoop{bulk, i, len, {i, M} => {
def XU = [bulk]u64
v:= XU ~~ loadBatch{xp, i, XV}
if (any{M{isSNaN{v}}}) return{0xffff_ffff} # is any not even a float
}}
return{0xffff_fffe}
}
int
}
v1:= toint{v0}
r1|= M{(broadcast{EV, ~E~~1} & EV~~v1) ^ EV~~(v1 >> (width{X}-1))}
}}
promote{u32, fold{|, r1}}
}
}
'avx2_squeeze_i8' = squeeze{256, i8 }
'avx2_squeeze_i16' = squeeze{256, i16}
'avx2_squeeze_i32' = squeeze{256, i32}
'avx2_squeeze_f64' = squeeze{256, f64}
'avx2_squeeze_B' = squeeze{256, u64}
#'avx2_squeeze_u16' = squeeze{256, u16}
#'avx2_squeeze_u32' = squeeze{256, u32}

View File

@ -58,6 +58,12 @@ def iota{T & w128{T,32}} = make{T,0,1,2,3}
def iota{T & w128{T,16}} = make{T,0,1,2,3,4,5,6,7}
def iota{T & w128{T,8}} = make{T,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}
# bit arith
def __xor{a:T, b:T & w128i{T}} = T ~~ emit{[4]f32, '_mm_xor_ps', v2f{a}, v2f{b}}
def __and{a:T, b:T & w128i{T}} = T ~~ emit{[4]f32, '_mm_and_ps', v2f{a}, v2f{b}}
def __or {a:T, b:T & w128i{T}} = T ~~ emit{[4]f32, '_mm_or_ps', v2f{a}, v2f{b}}
def __not{a:T & w128u{T}} = a ^ broadcast{T, ~cast{eltype{T},0}}
# signed comparison
def __eq{a:T,b:T & T==[16]i8 } = emit{[16]u8, '_mm_cmpeq_epi8', a, b}
def __eq{a:T,b:T & T==[ 8]i16} = emit{[ 8]u16, '_mm_cmpeq_epi16', a, b}
@ -79,10 +85,38 @@ def __gt{a:T,b:T & w128u{T}} = ~(a<=b)
# rest of comparison
def __ne{a:T,b:T & w128i{T}} = ~(b==a)
# shift
def __shl{a:T,b & w128i{T, 16} & knum{b}} = emit{T, '_mm_slli_epi16', a, b}
def __shl{a:T,b & w128i{T, 32} & knum{b}} = emit{T, '_mm_slli_epi32', a, b}
def __shl{a:T,b & w128i{T, 64} & knum{b}} = emit{T, '_mm_slli_epi64', a, b}
def __shr{a:T,b & w128u{T, 16} & knum{b}} = emit{T, '_mm_srli_epi16', a, b}
def __shr{a:T,b & w128u{T, 32} & knum{b}} = emit{T, '_mm_srli_epi32', a, b}
def __shr{a:T,b & w128u{T, 64} & knum{b}} = emit{T, '_mm_srli_epi64', a, b}
def __shr{a:T,b & w128s{T, 16} & knum{b}} = emit{T, '_mm_srai_epi16', a, b}
def __shr{a:T,b & w128s{T, 32} & knum{b}} = emit{T, '_mm_srai_epi32', a, b}
# no 64-bit arithmetic shift :/
def min{a:T,b:T & T==[ 8]i16} = emit{T, '_mm_min_epi16', a, b}; def max{a:T,b:T & T==[ 8]i16} = emit{T, '_mm_max_epi16', a, b}
def min{a:T,b:T & T==[16]u8 } = emit{T, '_mm_min_epu8', a, b}; def max{a:T,b:T & T==[16]u8 } = emit{T, '_mm_max_epu8', a, b}
# shuffle stuff
def shuf{I==[4]u32, x:T, n & w128{T} & knum{n}} = T ~~ emit{[4]i32, '_mm_shuffle_epi32', v2i{x}, n}
def sel{I, x:T, i:[16]i8 & w128{T} & w128{I, 8}} = T ~~ emit{[16]u8, '_mm_shuffle_epi8', v2i{x}, i}
# arith
def __add{a:T,b:T & w128i{T, 8}} = emit{T, '_mm_add_epi8', a, b}
def __add{a:T,b:T & w128i{T, 16}} = emit{T, '_mm_add_epi16', a, b}
def __add{a:T,b:T & w128i{T, 32}} = emit{T, '_mm_add_epi32', a, b}
def __add{a:T,b:T & w128i{T, 64}} = emit{T, '_mm_add_epi64', a, b}
def __sub{a:T,b:T & w128i{T, 8}} = emit{T, '_mm_sub_epi8', a, b}
def __sub{a:T,b:T & w128i{T, 16}} = emit{T, '_mm_sub_epi16', a, b}
def __sub{a:T,b:T & w128i{T, 32}} = emit{T, '_mm_sub_epi32', a, b}
def __sub{a:T,b:T & w128i{T, 64}} = emit{T, '_mm_sub_epi64', a, b}
# mask stuff
def getmask{x:T & w128{T, 8}} = emit{u32, '_mm_movemask_epi8', x}
def getmask{x:T & w128{T, 16}} = getmask{emit{[16]u8, '_mm_packs_epi16', x, broadcast{[8]u16, 0}}}

View File

@ -10,7 +10,8 @@ test/moreCfgs.sh path/to/mlochbaum/BQN // run "2+2" in a bunch of configurations
./BQN test/equal.bqn // fuzz-test 𝕨≡𝕩
./BQN test/copy.bqn // fuzz-test creating new arrays with elements copied from another
./BQN test/bitcpy.bqn // fuzz-test bit_cpy; requires a CBQN build with -DTEST_BITCPY
./BQN test/squeeze.bqn // fuzz-test squeezing; requires a CBQN build with -DEEQUAL_NEGZERO
./BQN test/squeezeValid.bqn // fuzz-test squeezing giving a correct result; requires a CBQN build with -DEEQUAL_NEGZERO
./BQN test/squeezeExact.bqn // fuzz-test squeezing giving the exact smallest result; requires a CBQN build with -DEEQUAL_NEGZERO
./BQN test/random.bqn // various random tests
make -C test/ffi // test FFI functionality; expects both regular and shared library CBQN builds to already exist

70
test/squeezeExact.bqn Normal file
View File

@ -0,0 +1,70 @@
EEqual, ClearRefs, Variation, Squeeze, Type •internal
u 100×(•UnixTime+1|100וMonoTime)@
r •MakeRand •Show u
ntn "bit""i8""i16""i32""f64"
ntv "Ab""Ai8""Ai16""Ai32""Af64"
nmin 0 -2¯1+8×23
nmax 1¯1+2¯1+8×23
int 11110
ctn "c8""c16""c32"
ctv "Ac8""Ac16""Ac32"
cmax 111411228×23
specF64 r •Import "utils.bqn"
spec specF64{}"a⍉𝕨"<"foo"
# currently tests an array of 0s, with only one outstanding element
Do { 𝕊:
val {r.Range𝕩𝕩}
{𝕊: r.Range spec}
{𝕊: (¯5+r.Range 10) + 2r.Range 64}
base 0
len 1+r.Range 23+r.Range 7
pos r.Range len
arr "Ah" Variation val(pos) len<base
tOuttIn {
1=•Type val?
msk ((¬int) valval) 1˜ (nminnmax)val
1msk/ntn "Ah""Af"msk/ntv;
(2•Type val) 1len?
msk {2•Type val? (1len) (val-@)<cmax; 0}
1msk/ctn "Ah""Af"msk/ctv;
# else
"h""fill" "Ah""Af"
}
tIn {'S'1𝕩}¨ "Inc"¨ tIn
{
arrv 𝕩 Variation arr
sq Squeeze arrv
sqt Type sq
sqt{
"arr"¯3sqt? ¯3sqt;
!"slice"¯5sqt ¯5sqt
}
¬ (arrv EEqual arr) (arrv EEqual sq) ((<sqt) tOut)?
•Out "Fail:"
•Show arrv
•Show sq
•Show Type sq
•Show tOut𝕩
•Exit 1
;@
}¨ tIn
ClearRefs@
}
n0a "Af64" Variation 111¯1×π00
n0b Squeeze n0a
{
n0a÷n0b? •Out "Squeeze behavior on negative zero: keep";
•Out "Squeeze behavior on negative zero: discard"
}
Do¨ 100000