diff --git a/build/src/build.bqn b/build/src/build.bqn index b03687f3..7b21fbcd 100755 --- a/build/src/build.bqn +++ b/build/src/build.bqn @@ -648,11 +648,11 @@ cachedBin‿linkerCache ← { "xa."‿"src/builtins/arithd.c"‿"dyarith", "xa."‿"src/builtins/cmp.c"‿"cmp", "xa."‿"src/builtins/squeeze.c"‿"squeeze", "xa."‿"src/utils/mut.c"‿"copy", "xa."‿"src/utils/bits.c"‿"bits", "xag"‿"src/builtins/transpose.c"‿"transpose", - "xag"‿"src/builtins/search.c"‿"search", "xa."‿"src/builtins/fold.c"‿"fold", + "xag"‿"src/builtins/search.c"‿"search", "xag"‿"src/builtins/selfsearch.c"‿"selfsearch" + "xag"‿"src/builtins/scan.c"‿"scan", "xa."‿"src/builtins/fold.c"‿"fold", "xag"‿"src/builtins/sort.c"‿"bins" - "2.."‿"src/builtins/select.c"‿"select", "xag"‿"src/builtins/scan.c"‿"scan", - "2a."‿"src/builtins/slash.c"‿"constrep", + "2.."‿"src/builtins/select.c"‿"select", "2a."‿"src/builtins/slash.c"‿"constrep", "xag"‿"src/builtins/slash.c"‿"slash", "2.."‿"src/builtins/slash.c"‿"count" ⟩ objs ← ⟨⟩ diff --git a/src/builtins/selfsearch.c b/src/builtins/selfsearch.c index 95bb32c7..8c5ed6bc 100644 --- a/src/builtins/selfsearch.c +++ b/src/builtins/selfsearch.c @@ -63,6 +63,19 @@ static inline u64 hash64(u64 x) { return x; } +static NOINLINE void memset32(u32* p, u32 v, usz l) { for (usz i=0; i20) msl=20; \ - usz sh = W - (msl<14? msl : 12+(msl&1)); /* Shift to fit to table */ \ - usz sz = 1 << (W - sh); /* Initial size */ \ - usz msz = 1ull << msl; /* Max sz */ \ - usz b = 64; /* Block size */ \ - /* Resize or abort if more than 1/2^thresh collisions/element */ \ - usz thresh = THRESH; \ - /* Filling e slots past the end requires e*(e+1)/2 collisions, so */ \ - /* n entries with <2 each can fill b? i+b : n; \ - for (; i < e; i++) { \ - T h = hash##W(xp[i]); T j0 = h>>sh, j = j0; T k; \ - while (k=hash[j], k!=h & k!=x0) j++; \ - cc += j-j0; \ - RESWRITE \ - } \ - if (i == n) break; \ - i64 dc = (i64)cc - ((THRESHMUL*i)>>thresh); \ - if (dc >= 0) { \ - if (sz == msz || (RAD && i < n/2 && sz >= 1<<18)) break; /*Abort*/ \ - /* Avoid resizing if close to the end */ \ - if (cc>(5+log-(W-sh))) continue;\ - /* Resize hash, factor of 4 */ \ - usz m = 2; \ - usz dif = sz*((1<>sh, k = k0; while (hash[k]!=x0) k++; \ - cc += k-k0; \ - hash[k] = h; AUXMOVE \ - } \ - if (cc >= STOP) break; \ - thresh = THRESH; \ - } \ - } \ - TFREE(halloc); \ - if (i==n) { decG(x); return RESULT; } -#define SELFHASHTAB_VAL(T, W, RAD, STOP, RES0, RESULT, RESWRITE, THRESHMUL, THRESH, INIT) \ - SELFHASHTAB(T, W, RAD, STOP, RES0, RESULT, RESWRITE, THRESHMUL, THRESH, \ - /*AUXSIZE*/sizeof(u32), \ - /* AUXINIT */ \ - u32* val = (u32*)(hash+sz+ext) + msz-sz; \ - memset32(val, 0, sz+ext); \ - INIT , \ - /*AUXEXTEND*/val -= dif; memset32(val, 0, dif); , \ - /*AUXMOVE*/u32 v = val[j]; val[j] = 0; val[k] = v;) - extern void (*const simd_mark_firsts_u8)(void*,uint64_t,void*,void*); extern u64 (*const simd_deduplicate_u8)(void*,uint64_t,void*,void*); @@ -268,14 +214,11 @@ B memberOf_c1(B t, B x) { } } if (lw==4) { if (n<8) { BRUTE(16); } else { LOOKUP(16); } } #undef LOOKUP - #define HASHTAB(T, W, RAD, STOP, THRESH) T* xp = (T*)xv; SELFHASHTAB( \ - T, W, RAD, STOP, \ - 1, taga(cpyBitArr(r)), hash[j]=h; rp[i]=k!=h;, \ - 1, THRESH, 0,,,) + #define TRY_HASHTAB(W) TRY_HASHTAB_RET(memberOf, W, taga(cpyBitArr(r))) if (lw==5) { if (n<12) { BRUTE(32); } i8* rp; B r = m_i8arrv(&rp, n); - HASHTAB(u32, 32, 1, n/2, sz==msz? 1 : sz>=(1<<15)? 3 : 5) + TRY_HASHTAB(32) // Radix-assisted lookup when hash table gives up usz rx = 256, tn = 1<<16; // Radix; table length @@ -301,10 +244,10 @@ B memberOf_c1(B t, B x) { if (lw==6 && canCompare64_norm(&x, &xv, n)) { if (n<20) { BRUTE(64); } i8* rp; B r = m_i8arrv(&rp, n); - HASHTAB(u64, 64, 0, n, sz==msz? 0 : sz>=(1<<18)? 0 : sz>=(1<<14)? 3 : 5) + TRY_HASHTAB(64) decG(r); // Fall through } - #undef HASHTAB + #undef TRY_HASHTAB #undef BRUTE if (RNK(x)>1) { @@ -377,18 +320,11 @@ B count_c1(B t, B x) { if (lw==3) { if (n<12) { BRUTE(8); } else { LOOKUP(8); } } if (lw==4) { if (n<12) { BRUTE(16); } else { LOOKUP(16); } } #undef LOOKUP - #define HASHTAB(T, W, RAD, STOP, THRESH) T* xp = (T*)xv; SELFHASHTAB_VAL( \ - T, W, RAD, STOP, \ - /*RES0*/0, /*RETURN*/num_squeeze(r), \ - /* RESWRITE */ \ - bool e0=h==x0; rp[i]=val[j]+(ctr0&-(u32)e0); \ - hash[j]=h; val[j]+=!e0; ctr0+=e0; , \ - /*THRESHMUL*/1, THRESH, \ - /*INIT*/u32 ctr0 = 1;) + #define TRY_HASHTAB(W) TRY_HASHTAB_RET(count, W, num_squeeze(r)) if (lw==5) { if (n<20) { BRUTE(32); } i32* rp; B r = m_i32arrv(&rp, n); - HASHTAB(u32, 32, 1, n/2, sz==msz? 1 : sz>=(1<<14)? 3 : 5) + TRY_HASHTAB(32) // Radix-assisted lookup usz rx = 256, tn = 1<<16; // Radix; table length u32* v0 = (u32*)xv; @@ -413,10 +349,10 @@ B count_c1(B t, B x) { if (lw==6 && canCompare64_norm(&x, &xv, n)) { if (n<20) { BRUTE(64); } i32* rp; B r = m_i32arrv(&rp, n); - HASHTAB(u64, 64, 0, n, sz==msz? 0 : sz>=(1<<18)? 0 : sz>=(1<<14)? 3 : 5) + TRY_HASHTAB(64) decG(r); // Fall through } - #undef HASHTAB + #undef TRY_HASHTAB #undef BRUTE if (RNK(x)>1) { @@ -485,18 +421,19 @@ B indexOf_c1(B t, B x) { if (lw==3) { if (n<12) { BRUTE(8); } else { LOOKUP(8); } } if (lw==4) { if (n<12) { BRUTE(16); } else { LOOKUP(16); } } #undef LOOKUP - - #define HASHTAB(T, W, THRESH) SELFHASHTAB_VAL(T, W, 0, 2*n, \ - /*RES0*/0, /*RETURN*/reduceI32WidthBelow(r, ctr), \ - /* RESWRITE */ \ - if (k!=h) { val[j]=ctr++; hash[j]=h; } rp[i]=val[j]; , \ - /*THRESHMUL*/2, THRESH, \ - /*INIT*/u32 ctr = 1;) + + #if SINGELI + #define TRY_HASHTAB(W) \ + u32 ctr = indexOf_c1_hash##W(rp, (u##W*)xv, n); \ + if (ctr>0) { decG(x); return reduceI32WidthBelow(r, ctr); } + #else + #define TRY_HASHTAB(W) (void)0; + #endif if (lw==5) { if (n<12) { BRUTE(32); } B r; i32* rp; r = m_i32arrv(&rp, n); - i32* xp = tyany_ptr(x); + i32* xp = xv; i32 min=I32_MAX, max=I32_MIN; for (usz i = 0; i < n; i++) { i32 c = xp[i]; @@ -512,17 +449,16 @@ B indexOf_c1(B t, B x) { decG(x); return reduceI32WidthBelow(r, u); } - HASHTAB(u32, 32, sz==msz? 0 : sz>=(1<<18)? 1 : sz>=(1<<14)? 4 : 6) + TRY_HASHTAB(32) decG(r); // Fall through } if (lw==6 && canCompare64_norm(&x, &xv, n)) { if (n<16) { BRUTE(64); } i32* rp; B r = m_i32arrv(&rp, n); - u64* xp = tyany_ptr(x); - HASHTAB(u64, 64, sz==msz? 0 : sz>=(1<<17)? 1 : sz>=(1<<13)? 4 : 6) + TRY_HASHTAB(64) decG(r); // Fall through } - #undef HASHTAB + #undef TRY_HASHTAB #undef BRUTE #undef DOTAB diff --git a/src/singeli/src/selfsearch.singeli b/src/singeli/src/selfsearch.singeli new file mode 100644 index 00000000..8b959ee0 --- /dev/null +++ b/src/singeli/src/selfsearch.singeli @@ -0,0 +1,138 @@ +include './base' +include './cbqnDefs' +local include 'skin/cext' + +# Size-specific functions defined in C +def hash_val{v:T} = emit{T, merge{'hash',fmtnat{width{T}}}, v} +def memset{p:pT, v, l} = { + emit{void, merge{'memset',fmtnat{elwidth{pT}}}, p, v, l} +} + +def names = tup{'memberOf', 'count', 'indexOf'} +def prims = tup{'∊', '⊒', '⊐' } +def map{f,t,v} = if (v==tupsel{0,f}) tupsel{0,t} else map{slice{f,1}, slice{t,1}, v} +def to_prim = map{names, prims, .} + +# Resizing hash table, with fallback +def rty{name} = if (to_prim{name}=='∊') i8 else i32 +fn selfhashtab{T, name}(rp:*rty{name}, xp:*T, n:usz) = { + def wt = width{T} + def prim = to_prim{name} + def has_radix = if (prim=='⊐') 0 else wt==32 + def {res0,stop} = if (prim=='∊') tup{1,n>>has_radix} else tup{0,2*n} + + log := clzc{n} + msl := clzc{n+n/2} + 1; if (has_radix and msl>20) msl=20 + sh := wt - tern{msl<14, msl, 12+(msl&1)} # Shift to fit to table + sz := usz~~1 << (wt - sh) # Initial size + msz := usz~~1 << msl # Max sz + b:usz = 64 # Block size + + # Resize or abort if more than 1/2^thresh collisions/element + def {set_thresh, div_thresh} = { + t:usz = 0 # Shift amount + def o = prim=='⊐' # Offset to allow effective t<0 + def div_thresh{i} = ((1<>t + def t0= has_radix*(prim=='∊') + def w = if (prim=='⊒') 1<<6 else wt<=(1<<24)/w) t = 0+o + else if ( sz>=(1<<20)/w) t = 3+o + else t = 5+o + } + tup{set_thresh, div_thresh} + } + set_thresh{} + + # Filling e slots past the end requires e*(e+1)/2 collisions, so + # n entries with <2 each can fill 0}; rp <-{0} res0 + def aux = prim!='∊' + halloc := talloc{u8, (msz+ext)*((wt + aux*width{u32})/8)} + szo := msz-sz # Beginning of allocation to initial table + sze := sz+ext # Initial table to end of allocation + hash := *T~~halloc + szo + memset{hash, x0, sze} + val := *u32~~(hash+sze) + szo + if (aux) { memset{val, 0, sze} } + + def {output, write_res} = match (prim) { + {('∊')} => tup{{b}=>b, {j,h,k,x0} => { hash<-{j}h; k!=h }} + {('⊒')} => { + ctr0:u32 = 1 + tup{ + {b}=>b, + {j,h,k,x0} => { + e0:=promote{u32,h==x0}; vj:=val->j; c0:=ctr0 + hash<-{j}h; val<-{j}vj+(e0^1); ctr0+=e0 + vj + (c0 & -e0) + } + } + } + {('⊐')} => { + ctr:u32 = 1 + tup{ + {b}=>promote{u32,b}*ctr, + {j,h,k,x0} => { + if (k!=h) { val<-{j}ctr; ++ctr; hash<-{j}h } + val->j + } + } + } + } + + def break=makelabel{} + cc:usz = 0 # Collision counter + i:usz=1; while (1) { + e := tern{n-i>b, i+b, n} + while (i < e) { + h := hash_val{xp->i}; j0 := h>>sh; j := j0 + k:=undefined{T}; while (((k=hash->j)!=h) & (k!=x0)) ++j + cc += cast_i{usz, j-j0} + rp <-{i} write_res{j,h,k,x0} + ++i + } + if (i == n) goto{break} + def p64{a} = i64~~promote{u64,a} + dc := p64{cc} - p64{div_thresh{i}} + if (dc >= 0) { + if (sz == msz) goto{break} # Abort + if (has_radix and i < n/2 and sz >= 1<<18) goto{break} + # Avoid resizing if close to the end + if (not (cc>(5+log-(wt-sh)))) { + # Resize hash, factor of 4 + def m = 2 + dif := sz*((1<j + if (h != x0) { + hash <-{j} x0 + k0 := h>>sh; k := k0; while (hash->k!=x0) ++k + cc += cast_i{usz, k-k0} + hash <-{k} h + if (aux) { v := val->j; val <-{j} 0; val <-{k} v } + } + } + if (cc >= stop) goto{break} + set_thresh{} + } + } + } + setlabel{break} + tfree{halloc} + output{i == n} # Whether it finished +} + +def exp_hash{T, name} = { + export{merge{name,'_c1_hash',fmtnat{width{T}}}, selfhashtab{T, name}} +} +each{{n}=>each{exp_hash{.,n},tup{u32,u64}}, names}