123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210(* Tables are manipulated by the C stubs (ml_physh.c), the layout of the
record must match. Creating a table does not call C: an empty table has no
storage, tables are allocated on first use. *)typetable={mutablewitness:Obj.t;(* a key of the minor table *)mutableminor_fill:int;mutableminor:Obj.t;(* young keys *)mutablecompactions:int;(* compaction count when major was hashed *)mutablemajor_fill:int;mutablemajor:Obj.t;(* other keys *)}letcreate_table()={witness=Obj.repr();minor_fill=0;minor=Obj.repr[||];compactions=0;major_fill=0;major=Obj.repr[||];}letlengtht=t.minor_fill+t.major_fillletis_emptyt=lengtht=0(* Sentinels: static values, outside of the OCaml heap *)externalsentinel_empty:unit->Obj.t="ml_physh_empty"[@@noalloc]externalsentinel_stale:unit->Obj.t="ml_physh_stale"[@@noalloc]letempty=sentinel_empty()letstale=sentinel_stale()(* Drop the storage *)letresett=t.witness<-Obj.repr();t.minor_fill<-0;t.minor<-Obj.repr[||];t.major_fill<-0;t.major<-Obj.repr[||](* Keep the storage: empty slots hold [empty].
A table without storage is the static [[||]], which must not be passed to
Array.fill (OCaml 4 asserts that the array is in the heap). *)letcleart=letfill(a:Obj.t)=letn=Obj.sizeainifn>0thenArray.fill(Obj.obja)0nemptyinfillt.minor;fillt.major;t.witness<-Obj.repr();t.minor_fill<-0;t.major_fill<-0(* Fast paths do not allocate. They fail when the table needs a sync
(after a minor collection or a compaction) or has to grow, the OCaml side
then calls a slow path. *)(* State of a key, as reported by the fast paths of sets.
Must match the enum in ml_physh.c. *)typestate=Absent|Present|Need_sync[@@warning"-37"](* built by C stubs *)moduleSet_core=structtype'at=tableexternalmem_fast:'at->'a->state="ml_physh_set_mem_fast"[@@noalloc]externalsync:'at->unit="ml_physh_set_sync"(* Add and return the prior state ([Need_sync]: nothing done) *)externaladd_fast:'at->'a->state="ml_physh_set_add_fast"[@@noalloc]externaladd_slow:'at->'a->bool="ml_physh_set_add_slow"(* Remove and return the prior state ([Need_sync]: nothing done) *)externalremove_fast:'at->'a->state="ml_physh_set_remove_fast"[@@noalloc]letcreate=create_tableletlength=lengthletis_empty=is_emptyletclear=clearletreset=resetletadd_if_newtx=matchadd_fasttxwith|Absent->false|Present->true|Need_sync->add_slowtxletaddtx=ignore(add_if_newtx:bool)letrecmemtx=matchmem_fasttxwith|Absent->false|Present->true|Need_sync->synct;memtxletrecremovetx=matchremove_fasttxwith|Absent|Present->()|Need_sync->synct;removetxend(* [fold_slots width f t acc] calls [f table i] for each used slot.
Iterating does not require a sync: the GC keeps the keys in the arrays
up-to-date, only their positions can be stale. Visit the major table,
then the minor table, as they were at the start.
A lookup in [f] can sync the table: flushing the minor table inserts its
entries in the major table, in place, while we are visiting it. Detect it
(the minor table is replaced) and from then on, skip the keys of the
minor table we started with: they are visited with the minor table. This
costs O(minor) only if a flush happens, the major table is never copied.
Modifying the table during the iteration is unspecified, but safe. *)letfold_slotswidthftacc=letmajor:Obj.tarray=Obj.objt.majorinletminor:Obj.tarray=Obj.objt.minorinletflushed=refNoneinletskipk=match!flushedwith|Somes->Set_core.memsk|Nonewhent.minor==Obj.reprminor->false|None->lets=Set_core.create()infori=0toArray.lengthminor/width-1doletk=Array.unsafe_getminor(i*width)inifk!=emptythenSet_core.addskdone;flushed:=Somes;Set_core.memskinletacc=refaccinfori=0toArray.lengthmajor/width-1doletk=Array.unsafe_getmajor(i*width)inifk!=empty&¬(skipk)thenacc:=fmajor(i*width)!accdone;fori=0toArray.lengthminor/width-1doifArray.unsafe_getminor(i*width)!=emptythenacc:=fminor(i*width)!accdone;!accmoduleSet=structincludeSet_coreletfoldftacc=fold_slots1(funtableiacc->f(Obj.obj(Array.unsafe_gettablei))acc)taccletiterft=fold(funx()->fx)t()endmoduleMap=structtype('a,'b)t=table(* Returns [empty] if the key is not bound, [stale] if a sync is needed *)externalfind_fast:('a,'b)t->'a->'b="ml_physh_map_find_fast"[@@noalloc]externalsync:('a,'b)t->unit="ml_physh_map_sync"(* [add_fast t k v overwrite] binds [k] to [v] if [k] is absent or
[overwrite] is true. Returns the previous binding, [empty] if there was
none, [stale] if nothing was done. [add_slow] never returns [stale]. *)externaladd_fast:('a,'b)t->'a->'b->bool->'b="ml_physh_map_add_fast"[@@noalloc]externaladd_slow:('a,'b)t->'a->'b->bool->'b="ml_physh_map_add_slow"(* Returns the previous binding, [empty] if there was none, [stale] if a
sync is needed (nothing done) *)externalremove_fast:('a,'b)t->'a->'b="ml_physh_map_remove_fast"[@@noalloc]letcreate=create_tableletlength=lengthletis_empty=is_emptyletclear=clearletreset=resetletadd_gentkvoverwrite=letr=add_fasttkvoverwriteinifObj.reprr==stalethenadd_slowtkvoverwriteelserletpriorr=ifObj.reprr==emptythenNoneelseSomerletadd_if_newtkv=prior(add_gentkvfalse)letadd_overwritetkv=prior(add_gentkvtrue)letaddtkv=ignore(add_gentkvtrue)letrecfindtk=letv=find_fasttkinifObj.reprv==emptythenraiseNot_found;ifObj.reprv==stalethen(synct;findtk)elsevletrecfind_opttk=letv=find_fasttkinifObj.reprv==emptythenNoneelseifObj.reprv==stalethen(synct;find_opttk)elseSomevletrecmemtk=letv=find_fasttkinifObj.reprv==stalethen(synct;memtk)elseObj.reprv!=emptyletrecremovetk=letr=remove_fasttkinifObj.reprr==stalethen(synct;removetk)letfoldftacc=fold_slots2(funtableiacc->f(Obj.obj(Array.unsafe_gettablei))(Obj.obj(Array.unsafe_gettable(i+1)))acc)taccletiterft=fold(funkv()->fkv)t()end