Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file ivar0.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474open!Coreopen!ImportmoduleScheduler=Scheduler1moduleCell=Types.Celltypeany=[`Empty|`Empty_one_handler|`Empty_one_or_more_handlers|`Full|`Indir]type'at='aTypes.Ivar.t={mutablecell:('a,any)cell}(* The ['b] is used to encode the constructor. This allows us to write functions that
take only one of the constructors, with no runtime test.
We maintain the invariant that the directed graph with ivars as nodes and [Indir]s as
edges is acyclic. The only functions that create an [Indir] are [squash] and
[connect], and for those, the target of the [Indir] is always a non-[Indir]. Thus, the
newly added edges are never part of a cycle. *)and('a,'b)cell=('a,'b)Types.Cell.t=|Empty_one_or_more_handlers:{(* [run] is mutable so we can set it to [ignore] when the handler is removed.
This is used when we install a handler on a full ivar since it is immediately
added to the scheduler. *)mutablerun:'a->unit;execution_context:Execution_context.t;(* [prev] and [next] circularly doubly link all handlers of the same ivar. *)mutableprev:('a,[`Empty_one_or_more_handlers])cell;mutablenext:('a,[`Empty_one_or_more_handlers])cell}->('a,[>`Empty_one_or_more_handlers])cell|Empty_one_handler:('a->unit)*Execution_context.t->('a,[>`Empty_one_handler])cell|Empty:('a,[>`Empty])cell|Full:'a->('a,[>`Full])cell|Indir:'at->('a,[>`Indir])cellmoduleHandler=structtype'at=('a,[`Empty_one_or_more_handlers])cellletrun(Empty_one_or_more_handlerst:_t)=t.runletexecution_context(Empty_one_or_more_handlerst:_t)=t.execution_contextletprev(Empty_one_or_more_handlerst:_t)=t.prevletnext(Empty_one_or_more_handlerst:_t)=t.nextletset_run(Empty_one_or_more_handlerst:_t)x=t.run<-xletset_prev(Empty_one_or_more_handlerst:_t)x=t.prev<-xletset_next(Empty_one_or_more_handlerst:_t)x=t.next<-xletcreaterunexecution_context=(* An optimized implementation of:
{[
let rec t =
Empty_one_or_more_handlers
{ run
; execution_context
; prev = t
; next = t }
in
h1 ]}
However the compilation of recursive value in OCaml is not optimal: the value is
allocated twice and copied once (with a loop calling caml_modify). This is not
necessary for simple recursive definitions like this one.
Instead we allocate the value with dummy fields and update them after. *)lett=Empty_one_or_more_handlers{run;execution_context;prev=Obj.magicNone;next=Obj.magicNone}inset_prevtt;set_nexttt;t;;letcreate2run1execution_context1run2execution_context2=(* An optimized implementation of:
{[
let rec t1 =
{ run = run1
; execution_context = execution_context1
; prev = t2
; next = t2 }
and t2 =
{ run = run2
; execution_context = execution_context2
; prev = t1
; next = t1 }
in
t1 ]} *)lett1=Empty_one_or_more_handlers{run=run1;execution_context=execution_context1;prev=Obj.magicNone;next=Obj.magicNone}inlett2=Empty_one_or_more_handlers{run=run2;execution_context=execution_context2;prev=t1;next=t1}inset_prevt1t2;set_nextt1t2;t1;;letinvariantt=Execution_context.invariant(execution_contextt);letr=ref(nextt)inwhilenot(phys_equal!rt)dolett1=!rinassert(phys_equal(prev(nextt1))t1);Execution_context.invariant(execution_contextt1);r:=next!rdone;;letis_singletont=phys_equalt(nextt)letlengtht=letn=ref1inletr=ref(nextt)inwhilenot(phys_equal!rt)doincrn;r:=next!rdone;!n;;letenqueuetschedulerv=Scheduler.enqueuescheduler(execution_contextt)(runt)vletschedule_jobstv=letscheduler=Scheduler.t()inenqueuetschedulerv;letr=ref(nextt)inwhilenot(phys_equal!rt)doenqueue!rschedulerv;r:=next!rdone;;letunlinkt=set_prev(nextt)(prevt);set_next(prevt)(nextt);set_prevtt;set_nexttt;;letaddtrunexecution_context=letresult=Empty_one_or_more_handlers{run;execution_context;prev=prevt;next=t}inset_next(prevt)result;set_prevtresult;result;;(* [splice t1 t2] creates:
{v
--> t1 <--> ... <--> last1 <--> t2 <--> ... <--> last2 <--
| |
----------------------------------------------------------
v} *)letsplicet1t2=letlast1=prevt1inletlast2=prevt2inset_nextlast1t2;set_nextlast2t1;set_prevt1last2;set_prevt2last1;;letof_listl=matchlwith|[]->None|(run,execution_context)::l->letfirst=createrunexecution_contextinletrecloopprevl=matchlwith|[]->set_prevfirstprev|(run,execution_context)::l->lett=Empty_one_or_more_handlers{run;execution_context;prev;next=first}inset_nextprevt;looptlinloopfirstl;Somefirst;;letto_listfirst=letreclooptacc=letacc=(runt,execution_contextt)::accinifphys_equaltfirstthenaccelseloop(prevt)accinloop(prevfirst)[];;letsexp_of_t_(t:_t)=let(Empty_one_or_more_handlers{run=_;execution_context;next=_;prev=_})=tin[%message(execution_context:Execution_context.t)];;endtype'aivar='at(* Compiled as the identity. *)letcell_of_handler:_Handler.t->_=function|Empty_one_or_more_handlers_asx->(x:>(_,any)cell);;letequal(t:_t)t'=phys_equaltt'letindirt={cell=Indirt}includeScheduler.Ivar(* [squash t] returns the non-[Indir] ivar at the end of the (possibly empty) chain of
[Indir]s starting with [t] and ensures that all [Indir]s along that chain are replaced
with an [Indir] pointing to the end of the chain. *)letsquash=letrecfollowindirt=(* [indir = Indir t] *)matcht.cellwith|Indirt'asindir'->followindir't'|_->indirinletrecupdatetindir=matcht.cellwith|Indirt'->t.cell<-indir;updatet'indir|_->tinfunt->matcht.cellwith|Indirt'->(matcht'.cellwith|Indirt''asindir->updatet(followindirt'')|_->t'(* nothing to do, since [t] is a chain with a single [Indir] *))|_->t;;(* nothing to do, since [t] isn't an [Indir]. *)letinvarianta_invariantt=lett=squashtinmatcht.cellwith|Indir_->assertfalse(* fulfilled by [squash] *)|Fulla->a_invarianta|Empty->()|Empty_one_handler(_,execution_context)->Execution_context.invariantexecution_context|Empty_one_or_more_handlers_ashandler->Handler.invarianthandler;;letsexp_of_tsexp_of_at:Sexp.t=lett=squashtinmatcht.cellwith|Indir_->assertfalse(* fulfilled by [squash] *)|Fulla->List[Atom"Full";sexp_of_aa]|Empty|Empty_one_handler_|Empty_one_or_more_handlers_->Atom"Empty";;letpeekt=lett=squashtinmatcht.cellwith|Indir_->assertfalse(* fulfilled by [squash] *)|Fulla->Somea|Empty|Empty_one_handler_|Empty_one_or_more_handlers_->None;;letvaluet~if_empty_then_failwith=lett=squashtinmatcht.cellwith|Indir_->assertfalse(* fulfilled by [squash] *)|Fulla->a|Empty|Empty_one_handler_|Empty_one_or_more_handlers_->failwithif_empty_then_failwith;;letvalue_exnt=valuet~if_empty_then_failwith:"Ivar.value_exn called on empty ivar"letis_emptyt=lett=squashtinmatcht.cellwith|Indir_->assertfalse(* fulfilled by [squash] *)|Full_->false|Empty|Empty_one_handler_|Empty_one_or_more_handlers_->true;;letis_fullt=not(is_emptyt)letfill_exntv=lett=squashtinmatcht.cellwith|Indir_->assertfalse(* fulfilled by [squash] *)|Full_->raise_s[%message"Ivar.fill_exn called on full ivar"(t:_t)]|Empty->t.cell<-Fullv|Empty_one_handler(run,execution_context)->t.cell<-Fullv;Scheduler.(enqueue(t()))execution_contextrunv|Empty_one_or_more_handlers_ashandler->t.cell<-Fullv;Handler.schedule_jobshandlerv;;letfill=fill_exnletremove_handlert(handler:_Handler.t)=Handler.set_runhandlerignore;lett=squashtinmatcht.cellwith|Indir_->assertfalse(* fulfilled by [squash] *)|Empty|Empty_one_handler_->(* These are only possible if [handler] was already removed. *)()|Full_->(* This is possible if [t] was filled before we try to remove the handler. E.g.
[Deferred.choose] will do this. *)()|Empty_one_or_more_handlers_ascell->ifHandler.is_singletonhandlerthent.cell<-Emptyelse(ifphys_equalhandlercellthent.cell<-cell_of_handler(Handler.nexthandler);Handler.unlinkhandler);;letadd_handlertrunexecution_context=lett=squashtinmatcht.cellwith|Indir_->assertfalse(* fulfilled by [squash] *)|Empty->lethandler=Handler.createrunexecution_contextint.cell<-cell_of_handlerhandler;handler|Empty_one_handler(run',execution_context')->lethandler=Handler.create2runexecution_contextrun'execution_context'int.cell<-cell_of_handlerhandler;handler|Empty_one_or_more_handlers_ashandler->Handler.addhandlerrunexecution_context|Fullv->lethandler=Handler.createrunexecution_contextin(* [run] calls [handler.run], which, if [handler] has been removed, has been changed
to [ignore]. *)letrunv=Handler.runhandlervinScheduler.(enqueue(t()))execution_contextrunv;handler;;lethas_handlerst=lett=squashtinmatcht.cellwith|Indir_->assertfalse(* fulfilled by [squash] *)|Empty_one_handler_|Empty_one_or_more_handlers_->true|Empty|Full_->false;;letupon'trun=add_handlertrunScheduler.(current_execution_context(t()))(* [upon] is conceptually the same as:
{[
let upon t f = ignore (upon' t run) ]}
However, below is a more efficient implementation, which is worth doing because [upon]
is very widely used and is so much more common than [upon']. The below implementation
avoids the use of the bag of handlers in the extremely common case of one handler for
the deferred. *)letupontrun=letscheduler=Scheduler.t()inletexecution_context=Scheduler.current_execution_contextschedulerinlett=squashtinmatcht.cellwith|Indir_->assertfalse(* fulfilled by [squash] *)|Fullv->Scheduler.enqueueschedulerexecution_contextrunv|Empty->t.cell<-Empty_one_handler(run,execution_context)|Empty_one_handler(run',execution_context')->t.cell<-cell_of_handler(Handler.create2runexecution_contextrun'execution_context')|Empty_one_or_more_handlers_ashandler->ignore(Handler.addhandlerrunexecution_context:_Handler.t);;(* [connect] takes ivars [bind_result] and [bind_rhs], and makes [bind_rhs]
be an [Indir] pointing to the non-indir cell reachable from [bind_result]. On entry
to [connect], [bind_result] and [bind_rhs] may be chains, since [bind_rhs] is an
arbitrary user-supplied deferred, and [bind_result] is returned to the user prior to
being [connect]ed, and may have been converted to an indirection in the case of
right-nested binds.
The purpose of [connect] is to make tail-recursive bind loops use constant space.
E.g.:
{[
let rec loop i =
if i = 0
then return ()
else (
let%bind () = after (sec 1.) in
loop (i - 1)) ]}
[connect] makes intermediate bind results all be [Indir]s pointing at the outermost
bind, rather than being a linear-length chain, with each pointing to the previous one.
Then, since the program is only holding on to the innermost and outermost binds all the
intermediate ones can be garbage collected.
[connect] works by squashing its arguments so that the [bind_rhs] always points at the
ultimate result. *)letconnect=(* [repoint_indirs ~ivar ~indir ~bind_result] repoints to [indir] all the ivars in the
chain reachable from [ivar], and returns the non-[Indir] cell at the end of the
chain. After repointing, we will merge the handlers in that cell with the handlers
in [bind_result], and put the merged set of handlers in [bind_result]. *)letrecrepoint_indirs~ivar~indir~bind_result=letcell=ivar.cellinmatchcellwith|Indirivar'->ivar.cell<-indir;repoint_indirs~ivar:ivar'~indir~bind_result|Full_->cell|Empty|Empty_one_handler_|Empty_one_or_more_handlers_->(* It is possible that [bind_result] and [bind_rhs] are not equal, but their chains
of indirs lead to the same non-[Indir] cell, in which case we cannot set that
cell to point to itself, because that would introduce a cycle. *)ifnot(phys_equalivarbind_result)thenivar.cell<-indir;cellinfun~bind_result~bind_rhs->ifnot(phys_equalbind_resultbind_rhs)then(letbind_result=squashbind_resultinletindir=Indirbind_resultinletbind_rhs_contents=repoint_indirs~ivar:bind_rhs~indir~bind_resultin(* update [bind_result] with the union of handlers in [bind_result] and
[bind_rhs] *)matchbind_result.cell,bind_rhs_contentswith|Indir_,_|_,Indir_->assertfalse(* fulfilled by [squash] and [repoint_indirs] *)(* [connect] is only used in bind, whose ivar is only ever exported as a read-only
deferred. Thus, [bind_result] must be empty. *)|Full_,_->assertfalse|_,Empty->()|Empty,_->bind_result.cell<-bind_rhs_contents|Empty_one_handler(run,execution_context),Fullv->bind_result.cell<-bind_rhs_contents;Scheduler.(enqueue(t()))execution_contextrunv|(Empty_one_or_more_handlers_ashandler),Fullv->bind_result.cell<-bind_rhs_contents;Handler.schedule_jobshandlerv|(Empty_one_handler(run1,execution_context1),Empty_one_handler(run2,execution_context2))->lethandler1=Handler.create2run1execution_context1run2execution_context2inbind_result.cell<-cell_of_handlerhandler1|((Empty_one_or_more_handlers_ashandler1),Empty_one_handler(run2,execution_context2))->ignore(Handler.addhandler1run2execution_context2:_Handler.t)|(Empty_one_handler(run1,execution_context1),(Empty_one_or_more_handlers_ashandler2))->lethandler1=Handler.addhandler2run1execution_context1inbind_result.cell<-cell_of_handlerhandler1|((Empty_one_or_more_handlers_ashandler1),(Empty_one_or_more_handlers_ashandler2))->Handler.splicehandler1handler2);;