123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639(** Create-only version of [Obj.t] *)moduleAny:sigtypetvalany:'a->tend=structtypet=Obj.tletany=Obj.reprendtype'aeval=|Eval_none|Eval_progress|Eval_someof'atype'at_=|Pureof'a|Impureof'a(* NOTE: is this really used anywhere? *)|Operator:{mutablevalue:'aeval;(* cached value *)mutabletrace:trace;(* list of parents this can invalidate *)mutabletrace_idx:trace_idx;(* list of direct children that can invalidate this *)desc:'adesc;}->'at_|Root:{mutablevalue:'aeval;(* cached value *)mutabletrace_idx:trace_idx;(* list of direct children that can invalidate this *)mutableon_invalidate:'a->unit;mutableacquired:bool;child:'at_;}->'at_and_desc=|Map:'at_*('a->'b)->'bdesc|Map2:'at_*'bt_*('a->'b->'c)->'cdesc|Pair:'at_*'bt_->('a*'b)desc|App:('a->'b)t_*'at_->'bdesc|Join:{child:'at_t_;mutableintermediate:'at_option}->'adesc|Var:{mutablebinding:'a}->'adesc|Prim:{acquire:unit->'a;release:'a->unit}->'adesc(* a set of (active) parents for a ['a t], used during invalidation *)andtrace=|T0|T1:_t_->trace|T2:_t_*_t_->trace|T3:_t_*_t_*_t_->trace|T4:_t_*_t_*_t_*_t_->trace|Tn:{mutableactive:int;mutablecount:int;mutableentries:Any.tt_array}->trace(* a set of direct children for a composite document *)andtrace_idx=|I0|I1:{mutableidx:int;obj:'at_;mutablenext:trace_idx}->trace_idx(* The type system cannot see that t is covariant in its parameter.
Use the Force to convince it. *)type+'atexternalinj:'at_->'at="%identity"externalprj:'at->'at_="%identity"externalprj2:'att->'at_t_="%identity"(* Basic combinators *)letreturnx=inj(Purex)letpurex=inj(Purex)letimpurex=inj(matchprjxwith|Purex->Impurex|other->other)letis_purex=matchprjxwith|Purex->Somex|_->Noneletdummy=Pure(Any.any())letoperatordesc=Operator{value=Eval_none;trace=T0;desc;trace_idx=I0}letmapfx=inj(matchprjxwith|Purevx->Pure(fvx)|x->operator(Map(x,f)))letmap2fxy=inj(matchprjx,prjywith|Purevx,Purevy->Pure(fvxvy)|x,y->operator(Map2(x,y,f)))letmap'xf=mapfxletmap2'xyf=map2fxyletpairxy=inj(matchprjx,prjywith|Purevx,Purevy->Pure(vx,vy)|x,y->operator(Pair(x,y)))letappfx=inj(matchprjf,prjxwith|Purevf,Purevx->Pure(vfvx)|f,x->operator(App(f,x)))letjoinchild=inj(matchprj2childwith|Purev->v|child->operator(Join{child;intermediate=None}))letbindxf=join(mapfx)(* Management of trace indices *)letaddrocobj=Printf.fprintfoc"0x%08x"(Obj.magicobj:int)externalt_equal:_t_->_t_->bool="%eq"externalobj_t:'at_->Any.tt_="%identity"letrecdump_trace:typea.at_->unit=funobj->matchobjwith|Pure_->Printf.eprintf"%a: Pure _\n%!"addrobj|Impure_->Printf.eprintf"%a: Impure _\n%!"addrobj|Operatort->Printf.eprintf"%a: Operator _ -> %a\n%!"addrobjdump_trace_auxt.trace;beginmatcht.tracewith|T0->()|T1a->dump_tracea|T2(a,b)->dump_tracea;dump_traceb|T3(a,b,c)->dump_tracea;dump_traceb;dump_tracec|T4(a,b,c,d)->dump_tracea;dump_traceb;dump_tracec;dump_traced|Tnt->Array.iterdump_tracet.entriesend|Root_->Printf.eprintf"%a: Root _\n%!"addrobjanddump_trace_auxoc=function|T0->Printf.fprintfoc"T0"|T1a->Printf.fprintfoc"T1 %a"addra|T2(a,b)->Printf.fprintfoc"T2 (%a, %a)"addraaddrb|T3(a,b,c)->Printf.fprintfoc"T3 (%a, %a, %a)"addraaddrbaddrc|T4(a,b,c,d)->Printf.fprintfoc"T4 (%a, %a, %a, %a)"addraaddrbaddrcaddrd|Tnt->Printf.fprintfoc"Tn {active = %d; count = %d; entries = "t.activet.count;Array.iter(Printf.fprintfoc"(%a)"addr)t.entries;Printf.fprintfoc"}"letdump_tracex=dump_trace(obj_t(prjx))letadd_idxobjidx=function|Pure_|Impure_->assertfalse|Roott'->t'.trace_idx<-I1{idx;obj;next=t'.trace_idx}|Operatort'->t'.trace_idx<-I1{idx;obj;next=t'.trace_idx}letrecrem_idx_recobj=function|I0->assertfalse|I1tasself->ift_equalt.objobjthen(t.idx,t.next)else(letidx,result=rem_idx_recobjt.nextint.next<-result;(idx,self))(* remove [obj] from the lwd's trace. *)letrem_idxobj=function|Pure_|Impure_->assertfalse|Roott'->letidx,trace_idx=rem_idx_recobjt'.trace_idxint'.trace_idx<-trace_idx;idx|Operatort'->letidx,trace_idx=rem_idx_recobjt'.trace_idxint'.trace_idx<-trace_idx;idx(* move [obj] from old index to new index. *)letrecmov_idx_recobjoldidxnewidx=function|I0->assertfalse|I1t->ift.idx=oldidx&&t_equalt.objobjthent.idx<-newidxelsemov_idx_recobjoldidxnewidxt.nextletmov_idxobjoldidxnewidx=function|Pure_|Impure_->assertfalse|Roott'->mov_idx_recobjoldidxnewidxt'.trace_idx|Operatort'->mov_idx_recobjoldidxnewidxt'.trace_idxletrecget_idx_recobj=function|I0->assertfalse|I1t->ift_equalt.objobjthent.idxelseget_idx_recobjt.next(* find index of [obj] in the given lwd *)letget_idxobj=function|Pure_|Impure_->assertfalse|Roott'->get_idx_recobjt'.trace_idx|Operatort'->get_idx_recobjt'.trace_idx(* Propagating invalidation recursively.
Each document is invalidated at most once,
and only if it has [t.value = Some _]. *)letrecinvalidate_node:typea.at_->unit=function|Pure_|Impure_->assertfalse|Root({value;_}ast)->t.value<-Eval_none;beginmatchvaluewith|Eval_none|Eval_progress->()|Eval_somex->t.on_invalidatex(* user callback that {i observes} this root. *)end|Operator{value=Eval_none;_}->()|Operatort->t.value<-Eval_none;invalidate_tracet.trace;(* invalidate parents recursively *)(* invalidate recursively documents in the given trace *)andinvalidate_trace=function|T0->()|T1x->invalidate_nodex|T2(x,y)->invalidate_nodex;invalidate_nodey|T3(x,y,z)->invalidate_nodex;invalidate_nodey;invalidate_nodez|T4(x,y,z,w)->invalidate_nodex;invalidate_nodey;invalidate_nodez;invalidate_nodew|Tnt->letactive=t.activeint.active<-0;fori=0toactive-1doinvalidate_nodet.entries.(i)done(* Variables *)type'avar='at_letvarx=operator(Var{binding=x})letgetx=injxletset(vx:_var)x:unit=matchvxwith|Operator({desc=Varv;_})->(* set the variable, and invalidate all observers *)invalidate_nodevx;v.binding<-x|_->assertfalseletpeek=function|Operator({desc=Varv;_})->v.binding|_->assertfalse(* Primitives *)type'aprim='atletprim~acquire~release=inj(operator(Prim{acquire;release}))letget_primx=xletinvalidatex=matchprjxwith|Operator({desc=Primp;_}ast)->letvalue=t.valueint.value<-Eval_none;(* the value is invalidated, be sure to invalidate all parents as well *)invalidate_tracet.trace;beginmatchvaluewith|Eval_none|Eval_progress->()|Eval_somev->p.releasevend|_->assertfalsetyperelease_list=|Release_done|Release_more:{origin:'at_;element:'bt_;next:release_list}->release_listtyperelease_queue=release_listrefletmake_release_queue()=refRelease_donetyperelease_failure=exn*Printexc.raw_backtrace(* [sub_release [] origin self] is called when [origin] is released,
where [origin] is reachable from [self]'s trace.
We're going to remove [origin] from that trace as [origin] is now dead.
[sub_release] cannot raise.
If a primitive raises, the exception is caught and a warning is emitted. *)letrecsub_release:typeab.release_failurelist->at_->bt_->release_failurelist=funfailuresorigin->function|Root_->assertfalse|Pure_|Impure_->failures|Operatortasself->(* compute [t.trace \ {origin}] *)lettrace=matcht.tracewith|T0->assertfalse|T1x->assert(t_equalxorigin);T0|T2(x,y)->ift_equalxoriginthenT1yelseift_equalyoriginthenT1xelseassertfalse|T3(x,y,z)->ift_equalxoriginthenT2(y,z)elseift_equalyoriginthenT2(x,z)elseift_equalzoriginthenT2(x,y)elseassertfalse|T4(x,y,z,w)->ift_equalxoriginthenT3(y,z,w)elseift_equalyoriginthenT3(x,z,w)elseift_equalzoriginthenT3(x,y,w)elseift_equalworiginthenT3(x,y,z)elseassertfalse|Tntnastrace->letrevidx=rem_idxselforigininassert(t_equaltn.entries.(revidx)origin);letcount=tn.count-1intn.count<-count;ifrevidx<countthen(letobj=tn.entries.(count)intn.entries.(revidx)<-obj;tn.entries.(count)<-dummy;mov_idxselfcountrevidxobj)elsetn.entries.(revidx)<-dummy;iftn.active>countthentn.active<-count;ifcount=4then((* downgrade to [T4] to save space *)leta=tn.entries.(0)andb=tn.entries.(1)inletc=tn.entries.(2)andd=tn.entries.(3)inignore(rem_idxselfa:int);ignore(rem_idxselfb:int);ignore(rem_idxselfc:int);ignore(rem_idxselfd:int);T4(a,b,c,d))else(letlen=Array.lengthtn.entriesinifcount<=lenlsr2thenTn{active=tn.active;count=tn.count;entries=Array.subtn.entries0(lenlsr1)}elsetrace)int.trace<-trace;matchtracewith|T0->(* [self] is not active anymore, since it's not reachable
from any root. We can release its cached value and
recursively release its subtree. *)letvalue=t.valueint.value<-Eval_progress;beginmatcht.descwith|Map(x,_)->sub_releasefailuresselfx|Map2(x,y,_)->sub_release(sub_releasefailuresselfx)selfy|Pair(x,y)->sub_release(sub_releasefailuresselfx)selfy|App(x,y)->sub_release(sub_releasefailuresselfx)selfy|Join({child;intermediate}ast)->letfailures=sub_releasefailuresselfchildinbeginmatchintermediatewith|None->failures|Somechild'->t.intermediate<-None;sub_releasefailuresselfchild'end|Var_->failures|Primt->beginmatchvaluewith|Eval_none|Eval_progress->failures|Eval_somex->beginmatcht.releasexwith|()->failures|exceptionexn->letbt=Printexc.get_raw_backtrace()in(exn,bt)::failuresendendend|_->failures(* [sub_acquire] cannot raise *)letrecsub_acquire:typeab.at_->bt_->unit=funorigin->function|Root_->assertfalse|Pure_|Impure_->()|Operatortasself->(* [acquire] is true if this is the first time this operator
is used, in which case we need to acquire its children *)letacquire=matcht.tracewithT0->true|_->falseinlettrace=matcht.tracewith|T0->T1origin|T1x->T2(origin,x)|T2(x,y)->T3(origin,x,y)|T3(x,y,z)->T4(origin,x,y,z)|T4(x,y,z,w)->letobj_origin=obj_torigininletentries=[|obj_tx;obj_ty;obj_tz;obj_tw;obj_origin;dummy;dummy;dummy|]infori=0to4doadd_idxselfientries.(i)done;Tn{active=5;count=5;entries}|Tntnastrace->letindex=tn.countinletentries,trace=(* possibly resize array [entries] *)ifindex<Array.lengthtn.entriesthen(tn.count<-tn.count+1;(tn.entries,trace))else(letentries=Array.make(index*2)dummyinArray.blittn.entries0entries0index;(entries,Tn{active=tn.active;count=index+1;entries}))inletobj_origin=obj_torigininentries.(index)<-obj_origin;add_idxselfindexobj_origin;traceint.trace<-trace;ifacquirethen((* acquire immediate children, and so on recursively *)matcht.descwith|Map(x,_)->sub_acquireselfx|Map2(x,y,_)->sub_acquireselfx;sub_acquireselfy|Pair(x,y)->sub_acquireselfx;sub_acquireselfy|App(x,y)->sub_acquireselfx;sub_acquireselfy|Join{child;intermediate}->sub_acquireselfchild;beginmatchintermediatewith|None->()|Some_->assertfalse(* this can't initialized already, first-time acquire *)end|Var_->()|Prim_->())(* make sure that [origin] is in [self.trace], passed as last arg. *)letactivate_tracingselforigin=function|Tntn->letidx=get_idxselforiginin(* index of [self] in [origin.trace_idx] *)letactive=tn.activein(* [idx < active] means [self] is already traced by [origin].
We only have to add [self] to the entries if [idx >= active]. *)ifidx>=activethen(tn.active<-active+1;);ifidx>activethen((* swap with last entry in [tn.entries] *)letold=tn.entries.(active)intn.entries.(idx)<-old;tn.entries.(active)<-obj_torigin;mov_idxselfactiveidxold;mov_idxselfidxactiveorigin)|_->()(* [sub_sample origin self] computes a value for [self].
[sub_sample] raise if any user-provided computation raises.
Graph will be left in a coherent state but exception will be propagated
to the observer. *)letsub_samplequeue=letrecaux:typeab.at_->bt_->b=funorigin->function|Root_->assertfalse|Purex|Impurex->x|Operatortasself->(* try to use cached value, if present *)matcht.valuewith|Eval_somevalue->activate_tracingselforigint.trace;value|_->t.value<-Eval_progress;letresult:b=matcht.descwith|Map(x,f)->f(auxselfx)|Map2(x,y,f)->f(auxselfx)(auxselfy)|Pair(x,y)->(auxselfx,auxselfy)|App(f,x)->(auxselff)(auxselfx)|Joinx->letintermediate=(* We haven't touched any state yet,
it is safe for [aux] to raise *)auxselfx.childinbeginmatchx.intermediatewith|None->x.intermediate<-Someintermediate;sub_acquireselfintermediate;|Somex'whenx'!=intermediate->queue:=Release_more{origin=self;element=x';next=!queue;};x.intermediate<-Someintermediate;sub_acquireselfintermediate;|Some_->()end;auxselfintermediate|Varx->x.binding|Primt->t.acquire()inbeginmatcht.valuewith|Eval_progress->t.value<-Eval_someresult;|Eval_none|Eval_some_->()end;(* [self] just became active, so it may invalidate [origin] in case its
value changes because of [t.desc], like if it's a variable and gets
mutated, or if it's a primitive that gets invalidated.
We need to put [origin] into [self.trace] in case it isn't there yet. *)activate_tracingselforigint.trace;resultinauxtype'aroot='atletobserve?(on_invalidate=ignore)child:_root=letroot=Root{child=prjchild;value=Eval_none;on_invalidate;trace_idx=I0;acquired=false;}ininjrootexceptionRelease_failureofexnoption*release_failurelistletraw_flush_release_queuequeue=letrecauxfailures=function|Release_done->failures|Release_moret->letfailures=sub_releasefailurest.origint.elementinauxfailurest.nextinaux[]queueletflush_release_queuequeue=letqueue'=!queueinqueue:=Release_done;raw_flush_release_queuequeue'letsamplequeuex=matchprjxwith|Pure_|Impure_|Operator_->assertfalse|Roottasself->matcht.valuewith|Eval_somevalue->value|_->(* no cached value, compute it now *)ifnott.acquiredthen(t.acquired<-true;sub_acquireselft.child;);t.value<-Eval_progress;letvalue=sub_samplequeueselft.childinbeginmatcht.valuewith|Eval_progress->t.value<-Eval_somevalue;(* cache value *)|Eval_none|Eval_some_->()end;valueletis_damagedx=matchprjxwith|Pure_|Impure_|Operator_->assertfalse|Root{value=Eval_some_;_}->false|Root{value=Eval_none|Eval_progress;_}->trueletreleasequeuex=matchprjxwith|Pure_|Impure_|Operator_->assertfalse|Roottasself->ift.acquiredthen((* release subtree, remove cached value *)t.value<-Eval_none;t.acquired<-false;queue:=Release_more{origin=self;element=t.child;next=!queue})letset_on_invalidatexf=matchprjxwith|Pure_|Impure_|Operator_->assertfalse|Roott->t.on_invalidate<-fletflush_or_failmain_exnqueue=matchflush_release_queuequeuewith|[]->()|failures->raise(Release_failure(main_exn,failures))letquick_sampleroot=letqueue=refRelease_doneinmatchsamplequeuerootwith|result->flush_or_failNonequeue;result|exceptionexn->flush_or_fail(Someexn)queue;raiseexnletquick_releaseroot=letqueue=refRelease_doneinreleasequeueroot;flush_or_failNonequeuemoduleInfix=structlet(>>=)=bindlet(>|=)=map'let(<*>)=append(*$R
let x = var 0 in
let y = map succ (get x) in
let o_y = Lwd.observe y in
assert_equal 1 (quick_sample o_y);
set x 10;
assert_equal 11 (quick_sample o_y);
*)