diff --git a/makefile b/makefile index 8cb01857..317394a9 100644 --- a/makefile +++ b/makefile @@ -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/^/ /' diff --git a/src/builtins/squeeze.c b/src/builtins/squeeze.c index 69f64750..35349382 100644 --- a/src/builtins/squeeze.c +++ b/src/builtins/squeeze.c @@ -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>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; diff --git a/src/singeli/src/avx.singeli b/src/singeli/src/avx.singeli index 307d959d..c1d49b41 100644 --- a/src/singeli/src/avx.singeli +++ b/src/singeli/src/avx.singeli @@ -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 diff --git a/src/singeli/src/avx2.singeli b/src/singeli/src/avx2.singeli index 3bd0b4c6..5f5e1366 100644 --- a/src/singeli/src/avx2.singeli +++ b/src/singeli/src/avx2.singeli @@ -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} \ No newline at end of file diff --git a/src/singeli/src/base.singeli b/src/singeli/src/base.singeli index d07a3b46..839b9518 100644 --- a/src/singeli/src/base.singeli +++ b/src/singeli/src/base.singeli @@ -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< { 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} \ No newline at end of file diff --git a/src/singeli/src/sse3.singeli b/src/singeli/src/sse3.singeli index 76c36648..be672717 100644 --- a/src/singeli/src/sse3.singeli +++ b/src/singeli/src/sse3.singeli @@ -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}}} diff --git a/test/README.md b/test/README.md index 6fcc2490..38f5fbab 100644 --- a/test/README.md +++ b/test/README.md @@ -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 diff --git a/test/squeezeExact.bqn b/test/squeezeExact.bqn new file mode 100644 index 00000000..78064a9a --- /dev/null +++ b/test/squeezeExact.bqn @@ -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×2⋆↕3 +nmax ← 1∾¯1+2⋆¯1+8×2⋆↕3 +int ← 1‿1‿1‿1‿0 + +ctn ← "c8"‿"c16"‿"c32" +ctv ← "Ac8"‿"Ac16"‿"Ac32" +cmax ← 1114112⌊2⋆8×2⋆↕3 + +⟨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) + 2⋆r.Range 64} + ⟩ + base ← 0 + + len ← 1+r.Range 2⋆3+r.Range 7 + pos ← r.Range len + arr ← "Ah" Variation val⌾(pos⊸⊑) len⥊