123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215(* Js_of_ocaml compiler
* http://www.ocsigen.org/js_of_ocaml/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, with linking exception;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*)(* The following CPS transform is based on the one proposed in D.
Hillerström, S. Lindley, R. Atkey, and K. C. Sivaramakrishnan,
“Continuation Passing Style for Effect Handlers” (FSCD 2017), with
adaptations to account for exception handlers (which are not
considered in detail in the paper) and for the fact that the
language is an SSA form rather than a classical lambda calculus.
Rather than using a stack of continuations, and effect and
exception handlers, only the current continuation is passed between
functions, while exception handlers and effect handlers are stored
in global variables. This avoid having to manipulate the stack each
time the current continuation changes. This also allows us to deal
with exceptions from the runtime or from JavaScript code (a [try
... with] at the top of stack can have access to the current
exception handler and resume the execution from there; see the
definition of runtime function [caml_callback]).
*)open!StdlibopenCodeletdebug=Debug.find"effects"letdouble_translate()=matchConfig.effects()with|`Disabled|`Jspi->assertfalse|`Cps->false|`Double_translation->trueletdebug_printfmt=ifdebug()thenFormat.(eprintf(fmt^^"%!"))elseFormat.(ifprintferr_formatterfmt)letget_edgesgsrc=tryHashtbl.findgsrcwithNot_found->Addr.Set.emptyletadd_edgegsrcdst=Hashtbl.replacegsrc(Addr.Set.adddst(get_edgesgsrc))letreverse_graphg=letg'=Hashtbl.create16inHashtbl.iter(funchildparents->Addr.Set.iter(funparent->add_edgeg'parentchild)parents)g;g'typecontrol_flow_graph={succs:(Addr.t,Addr.Set.t)Hashtbl.t;preds:(Addr.t,Addr.Set.t)Hashtbl.t;reverse_post_order:Addr.tlist;block_order:(Addr.t,int)Hashtbl.t}letbuild_graphblockspc=letsuccs=Hashtbl.create16inletl=ref[]inletvisited=Hashtbl.create16inletrectraversepc=ifnot(Hashtbl.memvisitedpc)then(Hashtbl.addvisitedpc();letsuccessors=Code.fold_childrenblockspcAddr.Set.addAddr.Set.emptyinHashtbl.addsuccspcsuccessors;Addr.Set.itertraversesuccessors;l:=pc::!l)intraversepc;letblock_order=Hashtbl.create16inList.iteri!l~f:(funipc->Hashtbl.addblock_orderpci);letpreds=reverse_graphsuccsin{succs;preds;reverse_post_order=!l;block_order}letdominator_treeg=(* A Simple, Fast Dominance Algorithm
Keith D. Cooper, Timothy J. Harvey, and Ken Kennedy *)letdom=Hashtbl.create16inletrecinterpcpc'=(* Compute closest common ancestor *)ifpc=pc'thenpcelseifHashtbl.findg.block_orderpc<Hashtbl.findg.block_orderpc'theninterpc(Hashtbl.finddompc')elseinter(Hashtbl.finddompc)pc'inList.iterg.reverse_post_order~f:(funpc->letl=Hashtbl.findg.succspcinAddr.Set.iter(funpc'->letd=tryinterpc(Hashtbl.finddompc')withNot_found->pcinHashtbl.replacedompc'd)l);(* Check we have reached a fixed point (reducible graph) *)List.iterg.reverse_post_order~f:(funpc->letl=Hashtbl.findg.succspcinAddr.Set.iter(funpc'->letd=Hashtbl.finddompc'inassert(interpcd=d))l);dom(* pc has at least two forward edges moving into it *)letis_merge_nodegpc=lets=tryHashtbl.findg.predspcwithNot_found->assertfalseinleto=Hashtbl.findg.block_orderpcinletn=Addr.Set.fold(funpc'n->ifHashtbl.findg.block_orderpc'<othenn+1elsen)s0inn>1letdominance_frontiergidom=letfrontiers=Hashtbl.create16inHashtbl.iter(funpcpreds->ifAddr.Set.cardinalpreds>1thenletdom=Hashtbl.findidompcinletreclooprunner=ifrunner<>domthen(add_edgefrontiersrunnerpc;loop(Hashtbl.findidomrunner))inAddr.Set.iterlooppreds)g.preds;frontiers(* Last instruction of a block, ignoring events *)letreclast_instrl=matchlwith|[]->None|[i]|[i;Event_]->Somei|_::rem->last_instrrem(* Split a block, separating the last instruction from the preceeding
ones, ignoring events *)letblock_split_lastxs=letrecauxacc=function|[]->None|[x]|[x;Event_]->Some(List.revacc,x)|x::xs->aux(x::acc)xsinaux[]xsletempty_bodyb=matchbwith|[]|[Event_]->true|_->false(****)leteffect_primitive_or_application=function|Prim(Extern("%resume"|"%perform"|"%reperform"),_)|Apply_->true|Block(_,_,_,_)|Field(_,_,_)|Closure(_,_)|Constant_|Prim(_,_)|Special_->false(*
We establish the list of blocks that needs to be CPS-transformed. We
also mark blocks that correspond to function continuations or
exception handlers. And we keep track of the exception handler
associated to each Poptrap, and possibly Raise.
*)letcompute_needed_transformations~cfg~idom~cps_needed~blocks~start=letfrontiers=dominance_frontiercfgidominlettransformation_needed=refAddr.Set.emptyinletmatching_exn_handler=Hashtbl.create16inletis_continuation=Hashtbl.create16inletrecmark_neededpc=(* If a block is transformed, all the blocks in its dominance
frontier needs to be transformed as well. *)ifnot(Addr.Set.mempc!transformation_needed)then(transformation_needed:=Addr.Set.addpc!transformation_needed;Addr.Set.itermark_needed(get_edgesfrontierspc))inletmark_continuationpcx=ifnot(Hashtbl.memis_continuationpc)thenHashtbl.addis_continuationpc(ifAddr.Set.mempc(get_edgesfrontierspc)then`Loopelse`Paramx)inletrectraversevisited~englobing_exn_handlerspc=ifAddr.Set.mempcvisitedthenvisitedelseletvisited=Addr.Set.addpcvisitedinletblock=Addr.Map.findpcblocksin(matchblock.branchwith|Branch(dst,_)->(matchlast_instrblock.bodywith|Some(Let(x,e))wheneffect_primitive_or_applicatione&&Var.Set.memxcps_needed->(* The block after a function application that needs to
be turned to CPS or an effect primitive needs to be
transformed. *)mark_neededdst;(* We need to transform the englobing exception handlers
as well *)List.iter~f:mark_neededenglobing_exn_handlers;mark_continuationdstx|_->())|Pushtrap(_,x,(handler_pc,_))->mark_continuationhandler_pcx|Poptrap_|Raise_->(matchenglobing_exn_handlerswith|handler_pc::_->Hashtbl.addmatching_exn_handlerpchandler_pc|_->())|_->());Code.fold_childrenblockspc(funpcvisited->letenglobing_exn_handlers=matchblock.branchwith|Pushtrap(_,_,(handler_pc,_))whenpc<>handler_pc->handler_pc::englobing_exn_handlers|Poptrap_->List.tlenglobing_exn_handlers|_->englobing_exn_handlersintraversevisited~englobing_exn_handlerspc)visitedinignore@@traverseAddr.Set.empty~englobing_exn_handlers:[]start;!transformation_needed,matching_exn_handler,is_continuation(****)(* Each block is turned into a function which is defined in the
dominator of the block. [closure_of_jump] provides the name of the
function correspoding to each block. [closures_of_alloc_site]
provides the list of functions which should be defined in a given
block. In case of double translation, the keys are the addresses of the
original (direct-style) blocks. Exception handlers are dealt with
separately.
*)typejump_closures={closure_of_jump:Var.tAddr.Map.t;closures_of_alloc_site:(Var.t*Addr.t)listAddr.Map.t}letjump_closuresblocks_to_transformidom:jump_closures=Hashtbl.fold(funnodeidom_nodejc->matchAddr.Set.memnodeblocks_to_transformwith|false->jc|true->letcname=Var.fresh()in{closure_of_jump=Addr.Map.addnodecnamejc.closure_of_jump;closures_of_alloc_site=Addr.Map.addidom_node((cname,node)::(tryAddr.Map.findidom_nodejc.closures_of_alloc_sitewithNot_found->[]))jc.closures_of_alloc_site})idom{closure_of_jump=Addr.Map.empty;closures_of_alloc_site=Addr.Map.empty}typetrampolined_calls=Var.Set.ttypein_cps=Var.Set.ttypest={mutablenew_blocks:Code.blockAddr.Map.t;mutablefree_pc:Code.Addr.t;blocks:Code.blockAddr.Map.t;cfg:control_flow_graph;jc:jump_closures;closure_info:(Addr.t,Var.tlist*(Addr.t*Var.tlist))Hashtbl.t(* Associates a function's address with its CPS parameters and CPS continuation *);cps_needed:Var.Set.t;blocks_to_transform:Addr.Set.t;is_continuation:(Addr.t,[`ParamofVar.t|`Loop])Hashtbl.t;matching_exn_handler:(Addr.t,Addr.t)Hashtbl.t;block_order:(Addr.t,int)Hashtbl.t;live_vars:Deadcode.variable_uses;flow_info:Global_flow.info;trampolined_calls:trampolined_callsref(* Call sites that require trampolining *);in_cps:in_cpsref(* Call sites whose callee must have a CPS component *);cps_pc_of_direct:(int,int)Hashtbl.t(* Mapping from direct-style to CPS addresses of functions (used when
double translation is enabled) *)}letadd_blockstblock=letfree_pc=st.free_pcinst.new_blocks<-Addr.Map.addfree_pcblockst.new_blocks;st.free_pc<-free_pc+1;free_pc(* Provide the address of the CPS translation of a block *)letmk_cps_pc_of_direct~stpc=ifdouble_translate()then(tryHashtbl.findst.cps_pc_of_directpcwithNot_found->letfree_pc=st.free_pcinst.free_pc<-free_pc+1;Hashtbl.addst.cps_pc_of_directpcfree_pc;free_pc)elsepcletcps_cont_of_direct~st(pc,args)=mk_cps_pc_of_direct~stpc,argsletclosure_of_pc~stpc=tryAddr.Map.findpcst.jc.closure_of_jumpwithNot_found->assertfalseletallocate_closure~st~params~body~branch=debug_print"@[<v>allocate_closure ~branch:(%a)@,@]"Code.Print.lastbranch;letblock={params=[];body;branch}inletpc=add_blockstblockinletname=Var.fresh()in[Let(name,Closure(params,(pc,[])))],namelettail_call~st?(instrs=[])~exact~in_cps~check~fargs=assert(exact||check);letret=Var.fresh()inifcheckthenst.trampolined_calls:=Var.Set.addret!(st.trampolined_calls);ifin_cpsthenst.in_cps:=Var.Set.addret!(st.in_cps);instrs@[Let(ret,Apply{f;args;exact})],Returnretletcps_branch~st~src(pc,args)=matchAddr.Set.mempcst.blocks_to_transformwith|false->[],Branch(mk_cps_pc_of_direct~stpc,args)|true->letargs,instrs=ifList.is_emptyargs&&Hashtbl.memst.is_continuationpcthen(* We are jumping to a block that is also used as a continuation.
We pass it a dummy argument. *)letx=Var.fresh()in[x],[Let(x,Constant(IntTargetint.zero))]elseargs,[]in(* We check the stack depth only for backward edges (so, at
least once per loop iteration) *)letcheck=Hashtbl.findst.block_ordersrc>=Hashtbl.findst.block_orderpcintail_call~st~instrs~exact:true~in_cps:false~check~f:(closure_of_pc~stpc)argsletcps_jump_cont~st~src((pc,_)ascont)=matchAddr.Set.mempcst.blocks_to_transformwith|false->cps_cont_of_direct~stcont|true->letcall_block=letbody,branch=cps_branch~st~srccontinadd_blockst{params=[];body;branch}incall_block,[]letallocate_continuation~st~alloc_jump_closures~split_closuressrc_pcxdirect_cont=debug_print"@[<v>allocate_continuation ~src_pc:%d ~cont:(%d,@ _)@,@]"src_pc(fstdirect_cont);(* We need to allocate an additional closure if [cont]
does not correspond to a continuation that binds [x].
This closure binds the return value [x], allocates
closures for dominated blocks and jumps to the next
block. When entering a loop, we also have to allocate a
closure to bind [x] if it is used in the loop body. In
other cases, we can just pass the closure corresponding
to the next block. *)letdirect_pc,args=direct_continif(matchargswith|[]->true|[x']->Var.equalxx'|_->false)&&matchHashtbl.findst.is_continuationdirect_pcwith|`Param_->true|`Loop->st.live_vars.(Var.idxx)=List.lengthargsthenalloc_jump_closures,closure_of_pc~stdirect_pcelseletbody,branch=cps_branch~st~src:src_pcdirect_continletinner_closures,outer_closures=(* For [Pushtrap], we need to separate the closures
corresponding to the exception handler body (that may make
use of [x]) from the other closures that may be used outside
of the exception handler. *)ifnotsplit_closuresthenalloc_jump_closures,[]elseifis_merge_nodest.cfgdirect_pcthen[],alloc_jump_closureselseList.partition~f:(funi->matchiwith|Let(_,Closure(_,(pc'',[])))->pc''=mk_cps_pc_of_direct~stdirect_pc|_->assertfalse)alloc_jump_closuresinletbody,branch=allocate_closure~st~params:[x]~body:(inner_closures@body)~branchinouter_closures@body,branchletcps_last~st~alloc_jump_closurespc(last:last)~k:instrlist*last=matchlastwith|Returnx->assert(List.is_emptyalloc_jump_closures);(* If the number of successive 'returns' is unbounded in CPS, it
means that we have an unbounded of calls in direct style
(even with tail call optimization) *)tail_call~st~exact:true~in_cps:false~check:false~f:k[x]|Raise(x,rmode)->(assert(List.is_emptyalloc_jump_closures);matchHashtbl.find_optst.matching_exn_handlerpcwith|Somepcwhennot(Addr.Set.mempcst.blocks_to_transform)->(* We are within a try ... with which is not
transformed. We should raise an exception normally *)[],last|_->letexn_handler=Var.fresh_n"raise"inletx,instrs=matchrmodewith|`Notrace->x,[]|(`Normal|`Reraise)asm->letx'=Var.forkxinletforce=matchmwith|`Normal->true|`Reraise->falseinleti=[Let(x',Prim(Extern"caml_maybe_attach_backtrace",[Pvx;Pc(Int(ifforcethenTargetint.oneelseTargetint.zero))]))]inx',iintail_call~st~instrs:(Let(exn_handler,Prim(Extern"caml_pop_trap",[]))::instrs)~exact:true~in_cps:false~check:false~f:exn_handler[x])|Stop->assert(List.is_emptyalloc_jump_closures);[],Stop|Branchcont->letbody,branch=cps_branch~st~src:pccontinalloc_jump_closures@body,branch|Cond(x,cont1,cont2)->(alloc_jump_closures,Cond(x,cps_jump_cont~st~src:pccont1,cps_jump_cont~st~src:pccont2))|Switch(x,c1)->(* To avoid code duplication during JavaScript generation, we need
to create a single block per continuation *)letcps_jump_cont=Fun.memoize(funx->cps_jump_cont~st~src:pcx)inalloc_jump_closures,Switch(x,Array.mapc1~f:cps_jump_cont)|Pushtrap(body_cont,exn,((handler_pc,_)ashandler_cont))->(assert(Hashtbl.memst.is_continuationhandler_pc);matchAddr.Set.memhandler_pcst.blocks_to_transformwith|false->letbody_cont=cps_cont_of_direct~stbody_continlethandler_cont=cps_cont_of_direct~sthandler_continletlast=Pushtrap(body_cont,exn,handler_cont)inalloc_jump_closures,last|true->letconstr_cont,exn_handler=allocate_continuation~st~alloc_jump_closures~split_closures:truepcexnhandler_continletpush_trap=Let(Var.fresh(),Prim(Extern"caml_push_trap",[Pvexn_handler]))inletbody,branch=cps_branch~st~src:pcbody_continconstr_cont@(push_trap::body),branch)|Poptrapcont->(matchAddr.Set.mem(Hashtbl.findst.matching_exn_handlerpc)st.blocks_to_transformwith|false->alloc_jump_closures,Poptrap(cps_jump_cont~st~src:pccont)|true->letexn_handler=Var.fresh()inletbody,branch=cps_branch~st~src:pccontin(alloc_jump_closures@(Let(exn_handler,Prim(Extern"caml_pop_trap",[]))::body),branch))letrewrite_instr~st(instr:instr):instr=matchinstrwith|Let(x,Closure(_,(pc,_)))whenVar.Set.memxst.cps_needed->(* When CPS-transforming with double translation enabled, there are no closures in
code that requires transforming, due to lambda lifiting. *)assert(not(double_translate()));(* Add the continuation parameter, and change the initial block if
needed *)letcps_params,cps_cont=Hashtbl.findst.closure_infopcinst.in_cps:=Var.Set.addx!(st.in_cps);Let(x,Closure(cps_params,cps_cont))|Let(x,Prim(Extern"caml_alloc_dummy_function",[size;arity]))->(matcharitywith|Pc(Inta)->Let(x,Prim(Extern"caml_alloc_dummy_function",[size;Pc(Int(Targetint.succa))]))|_->assertfalse)|Let(x,Apply{f;args;exact})whennot(Var.Set.memxst.cps_needed)->ifdouble_translate()thenletexact=(* If this function is unknown to the global flow analysis, then it was
introduced by the lambda lifting and we don't have exactness info any more. *)exact||Var.idxf<Var.Tbl.lengthst.flow_info.info_approximation&&Global_flow.exact_callst.flow_infof(List.lengthargs)inLet(x,Apply{f;args;exact})else((* At the moment, we turn into CPS any function not called with
the right number of parameter *)assert(Global_flow.exact_callst.flow_infof(List.lengthargs));Let(x,Apply{f;args;exact=true}))|Let(_,e)wheneffect_primitive_or_applicatione->(* For the CPS target, applications of CPS functions and effect primitives require
more work (allocating a continuation and/or modifying end-of-block branches) and
are handled in a specialized function. *)assertfalse|_->instrletcall_exactflow_info(f:Var.t)nargs:bool=(* If [f] is unknown to the global flow analysis, then it was introduced by
the lambda lifting and we don't have exactness about it. *)Var.idxf<Var.Tbl.lengthflow_info.Global_flow.info_approximation&&Global_flow.exact_callflow_infofnargsletcps_instr~st(instr:instr):instrlist=matchinstrwith|Let(x,Prim(Extern"caml_assume_no_perform",[Pvf]))whendouble_translate()->(* When double translation is enabled, we just call [f] in direct style.
Otherwise, the runtime primitive is used. *)letunit=Var.fresh_n"unit"in[Let(unit,Constant(IntTargetint.zero));Let(x,Apply{exact=call_exactst.flow_infof1;f;args=[unit]})]|_->[rewrite_instr~stinstr]letcps_block~st~k~orig_pcblock=debug_print"cps_block %d\n"orig_pc;debug_print"cps pc evaluates to %d\n"(mk_cps_pc_of_direct~storig_pc);letalloc_jump_closures=matchAddr.Map.findorig_pcst.jc.closures_of_alloc_sitewith|to_allocate->List.mapto_allocate~f:(fun(cname,jump_pc)->letparams=letjump_block=Addr.Map.findjump_pcst.blocksin(* For a function to be used as a continuation, it needs
exactly one parameter. So, we add a parameter if
needed. *)ifList.is_emptyjump_block.params&&Hashtbl.memst.is_continuationjump_pcthen(* We reuse the name of the value of the tail call of
one a the previous blocks. When there is a single
previous block, this is exactly what we want. For a
merge node, the variable is not used so we can just
as well use it. For a loop, we don't want the
return value of a call right before entering the
loop to be overriden by the value returned by the
last call in the loop. So, we may need to use an
additional closure to bind it, and we have to use a
fresh variable here *)letx=matchHashtbl.findst.is_continuationjump_pcwith|`Paramx->x|`Loop->Var.fresh()in[x]elsejump_block.paramsinletcps_jump_pc=mk_cps_pc_of_direct~stjump_pcinLet(cname,Closure(params,(cps_jump_pc,[]))))|exceptionNot_found->[]inletrewrite_last_instr(x:Var.t)(e:expr):(k:Var.t->instrlist*last)option=letperform_effect~effect_continuation_and_tail=Some(fun~k->lete=matchConfig.target()with|`JavaScript->(matchcontinuation_and_tailwith|None->Prim(Extern"caml_perform_effect",[Pveffect_;Pvk])|Some(continuation,tail)->Prim(Extern"caml_reperform_effect",[Pveffect_;continuation;tail;Pvk]))|`Wasm->((* temporary until we finish the change to the wasmoo
runtime *)matchcontinuation_and_tailwith|None->Prim(Extern"caml_perform_effect",[Pveffect_;Pc(IntTargetint.zero);Pc(IntTargetint.zero);Pvk])|Some(continuation,tail)->Prim(Extern"caml_perform_effect",[Pveffect_;continuation;tail;Pvk]))inletx=Var.fresh()in[Let(x,e)],Returnx)inmatchewith|Apply{f;args;exact}whenVar.Set.memxst.cps_needed->Some(fun~k->letexact=exact||call_exactst.flow_infof(List.lengthargs)intail_call~st~exact~in_cps:true~check:true~f(args@[k]))|Prim(Extern"%resume",[Pvstack;Pvf;Pvarg;tail])->Some(fun~k->letk'=Var.fresh_n"cont"intail_call~st~instrs:[Let(k',Prim(Extern"caml_resume_stack",[Pvstack;tail;Pvk]))]~exact:(call_exactst.flow_infof1)~in_cps:true~check:true~f[arg;k'])|Prim(Extern"%perform",[Pveffect_])->perform_effect~effect_None|Prim(Extern"%reperform",[Pveffect_;continuation;tail])->perform_effect~effect_(Some(continuation,tail))|_->Noneinletrewritten_block=matchblock_split_lastblock.body,block.branchwith|Some(body_prefix,Let(x,e)),Returnret->Option.map(rewrite_last_instrxe)~f:(funf->assert(List.is_emptyalloc_jump_closures);assert(Var.equalxret);letinstrs,branch=f~kinbody_prefix,instrs,branch)|Some(body_prefix,Let(x,e)),Branchcont->Option.map(rewrite_last_instrxe)~f:(funf->letconstr_cont,k'=allocate_continuation~st~alloc_jump_closures~split_closures:falseorig_pcxcontinletinstrs,branch=f~k:k'inbody_prefix,constr_cont@instrs,branch)|Some(_,(Event_|Set_field_|Offset_ref_|Array_set_|Assign_)),_|Some_,(Raise_|Stop|Cond_|Switch_|Pushtrap_|Poptrap_)|None,_->Noneinletbody,last=matchrewritten_blockwith|Some(body_prefix,last_instrs,last)->letbody_prefix=List.mapbody_prefix~f:(funi->cps_instr~sti)|>List.concatinbody_prefix@last_instrs,last|None->letlast_instrs,last=cps_last~st~alloc_jump_closuresorig_pcblock.branch~kinletbody=List.mapblock.body~f:(funi->cps_instr~sti)|>List.concatinbody@last_instrs,lastin{params=(ifAddr.Set.memorig_pcst.blocks_to_transformthen[]elseblock.params);body;branch=last}(* If double-translating, modify all function applications and closure
creations to take into account the fact that some closures must now have a
CPS version. Also rewrite the effect primitives to switch to the CPS version
of functions (for resume) or fail (for perform).
If not double-translating, then just add continuation arguments to function
definitions, and mark as exact all non-CPS calls. *)letrewrite_direct_block~st~cps_needed~closure_info~pcblock=debug_print"@[<v>rewrite_direct_block %d@,@]"pc;ifdouble_translate()thenletrewrite_instr=function|Let(x,Closure(params,((pc,_)ascont)))whenVar.Set.memxcps_needed->letdirect_c=Var.forkxinletcps_c=Var.forkxinletcps_params,cps_cont=Hashtbl.findclosure_infopcin[Let(direct_c,Closure(params,cont));Let(cps_c,Closure(cps_params,cps_cont));Let(x,Prim(Extern"caml_cps_closure",[Pvdirect_c;Pvcps_c]))]|Let(x,Prim(Extern"%resume",[stack;f;arg;tail]))->[Let(x,Prim(Extern"caml_resume",[f;arg;stack;tail]))]|Let(x,Prim(Extern"%perform",[effect_]))->(* In direct-style code, we just raise [Effect.Unhandled]. *)[Let(x,Prim(Extern"caml_raise_unhandled",[effect_]))]|Let(x,Prim(Extern"%reperform",[effect_;_continuation;_tail]))->(* Similar to previous case *)[Let(x,Prim(Extern"caml_raise_unhandled",[effect_]))]|Let(x,Prim(Extern"caml_assume_no_perform",[Pvf]))->(* We just need to call [f] in direct style. *)letunit=Var.fresh_n"unit"inletunit_val=IntTargetint.zeroinletexact=call_exactst.flow_infof1in[Let(unit,Constantunit_val);Let(x,Apply{exact;f;args=[unit]})]|(Let_|Assign_|Set_field_|Offset_ref_|Array_set_|Event_)asinstr->[instr]inletbody=List.concat_mapblock.body~f:(funi->rewrite_instri)in{blockwithbody}else{blockwithbody=List.map~f:(rewrite_instr~st)block.body}(* Apply a substitution in a set of blocks, including to bound variables *)letsubst_bound_in_blocksblockss=Addr.Map.mapi(funpcblock->ifdebug()then(debug_print"@[<v>block before first subst: @,";Code.Print.block(fun__->"")pcblock;debug_print"@]");letres=Subst.Including_Binders.blocksblockinifdebug()then(debug_print"@[<v>block after first subst: @,";Code.Print.block(fun__->"")pcres;debug_print"@]");res)blocksletsubst_add_fresharrayv=array.(Var.idxv)<-Var.forkvletcps_transform~live_vars~flow_info~cps_neededp=letclosure_info=Hashtbl.create16inlettrampolined_calls=refVar.Set.emptyinletin_cps=refVar.Set.emptyinletcps_pc_of_direct=Hashtbl.create512inletcloned_vars=Array.init(Var.count())~f:Var.of_idxinletcloned_subst=Subst.from_arraycloned_varsinletp=Code.fold_closures_innermost_firstp(funname_optparams(start,args)({Code.blocks;free_pc;_}asp)->Option.itername_opt~f:(funv->debug_print"@[<v>cname = %s@,@]"@@Var.to_stringv);(* We speculatively add a block at the beginning of the
function. In case of tail-recursion optimization, the
function implementing the loop body may have to be placed
there. *)letinitial_start=startinletstart',blocks'=(free_pc,Addr.Map.addfree_pc{params=[];body=[];branch=Branch(start,args)}blocks)inletcfg=build_graphblocks'start'inletidom=dominator_treecfginletshould_compute_needed_transformations=matchname_optwith|Somename->Var.Set.memnamecps_needed|None->(* We need to handle the CPS calls that are at toplevel, except
if we double-translate (in which case they are like all other
CPS calls from direct code). *)not(double_translate())inletblocks_to_transform,matching_exn_handler,is_continuation=ifshould_compute_needed_transformationsthencompute_needed_transformations~cfg~idom~cps_needed~blocks:blocks'~start:start'elseAddr.Set.empty,Hashtbl.create1,Hashtbl.create1inletclosure_jc=jump_closuresblocks_to_transformidominletstart,args,blocks,free_pc=(* Insert an initial block if needed. *)ifshould_compute_needed_transformations&&Addr.Map.memstart'closure_jc.closures_of_alloc_sitethenstart',[],blocks',free_pc+1elsestart,args,blocks,free_pcinletst={new_blocks=Addr.Map.empty;free_pc;blocks;cfg;jc=closure_jc;closure_info;cps_needed;blocks_to_transform;is_continuation;matching_exn_handler;block_order=cfg.block_order;flow_info;live_vars;trampolined_calls;in_cps;cps_pc_of_direct}inletfunction_needs_cps=matchname_optwith|Some_->should_compute_needed_transformations|None->(* Toplevel code: if we double-translate, no need to handle it
specially: CPS calls in it are like all other CPS calls from
direct code. Otherwise, it needs to wrapped within a
[caml_callback], but only if it performs CPS calls. *)not(double_translate()||Addr.Set.is_emptyblocks_to_transform)inifdebug()then(Format.eprintf"======== %b@."function_needs_cps;Code.preorder_traverse{fold=Code.fold_children}(funpc_->ifAddr.Set.mempcblocks_to_transformthenFormat.eprintf"CPS@.";letblock=Addr.Map.findpcblocksinCode.Print.block(fun_xi->Partial_cps_analysis.annotcps_neededxi)pcblock)startblocks());letblocks=(* For every block in the closure,
1. CPS-translate it if needed. If we double-translate, add its CPS
translation to the block map at a fresh address. Otherwise,
just replace the original block.
2. If we double-translate, keep the direct-style block but modify function
definitions to add the CPS version where needed, and turn uses of %resume
and %perform into switchings to CPS. *)lettransform_block=iffunction_needs_cps&&double_translate()then(letk=Var.fresh_n"cont"inletcps_start=mk_cps_pc_of_direct~ststartinList.iter~f:(subst_add_freshcloned_vars)params;letparams'=List.map~f:cloned_substparamsinletcps_args=List.map~f:cloned_substargsinHashtbl.addst.closure_infoinitial_start(params'@[k],(cps_start,cps_args));funpcblock->letcps_block=cps_block~st~k~orig_pc:pcblockin(rewrite_direct_block~st~cps_needed~closure_info:st.closure_info~pcblock,Somecps_block))elseiffunction_needs_cps&¬(double_translate())then(letk=Var.fresh_n"cont"inHashtbl.addst.closure_infoinitial_start(params@[k],(start,args));funpcblock->cps_block~st~k~orig_pc:pcblock,None)elsefunpcblock->(rewrite_direct_block~st~cps_needed~closure_info:st.closure_info~pcblock,None)inCode.traverse{fold=Code.fold_children}(funpcblocks->letblock,cps_block_opt=transform_blockpc(Addr.Map.findpcblocks)inletblocks=Addr.Map.addpcblockblocksinmatchcps_block_optwith|None->blocks|Someb->letcps_pc=mk_cps_pc_of_direct~stpcinst.new_blocks<-Addr.Map.addcps_pcbst.new_blocks;Addr.Map.addcps_pcbblocks)startst.blocksst.blocksin(* If double-translating, all variables bound in the CPS version will have to be
subst with fresh ones to avoid clashing with the definitions in the original
blocks (the actual substitution is done later). *)letnew_blocks=iffunction_needs_cps&&double_translate()then(Code.traverseCode.{fold=fold_children}(funpc()->letblock=Addr.Map.findpcp.blocksinFreevars.iter_block_bound_vars(funv->subst_add_freshcloned_varsv)block)initial_startp.blocks();subst_bound_in_blocksst.new_blockscloned_subst)elsest.new_blocksinletblocks=Addr.Map.foldAddr.Map.addnew_blocksblocksin{pwithblocks;free_pc=st.free_pc})pin(* Also apply our substitution to the sets of trampolined calls, and cps call sites *)trampolined_calls:=Var.Set.mapcloned_subst!trampolined_calls;in_cps:=Var.Set.mapcloned_subst!in_cps;letp=ifdouble_translate()thenpelsematchHashtbl.find_optclosure_infop.startwith|None->p|Some(cps_params,cps_cont)->(* Call [caml_callback] to set up the execution context. *)letnew_start=p.free_pcinletblocks=letmain=Var.fresh()inletargs=Var.fresh()inletres=Var.fresh()inAddr.Map.addnew_start{params=[];body=[Let(main,Closure(cps_params,cps_cont));Let(args,Prim(Extern"%js_array",[]));Let(res,Prim(Extern"caml_callback",[Pvmain;Pvargs]))];branch=Returnres}p.blocksin{start=new_start;blocks;free_pc=new_start+1}inp,!trampolined_calls,!in_cps(****)letcurrent_loop_headerfrontiersin_looppc=(* We remain in a loop while the loop header is in the dominance frontier.
We enter a loop when the block is in its dominance frontier. *)letfrontier=get_edgesfrontierspcinmatchin_loopwith|SomeheaderwhenAddr.Set.memheaderfrontier->in_loop|_->ifAddr.Set.mempcfrontierthenSomepcelseNoneletwrap_call~cps_neededpxfargsaccu=letarg_array=Var.fresh()in(p,Var.Set.removexcps_needed,[Let(arg_array,Prim(Extern"%js_array",List.map~f:(funy->Pvy)args));Let(x,Prim(Extern"caml_callback",[Pvf;Pvarg_array]))]::accu)letwrap_primitive~cps_needed(p:program)xeaccu=letf=Var.fresh()inletclosure_pc=p.free_pcin({pwithfree_pc=p.free_pc+1;blocks=Addr.Map.addclosure_pc(lety=Var.fresh()in{params=[];body=[Let(y,e)];branch=Returny})p.blocks},Var.Set.removex(Var.Set.addfcps_needed),letargs=Var.fresh()in[Let(f,Closure([],(closure_pc,[])));Let(args,Prim(Extern"%js_array",[]));Let(x,Prim(Extern"caml_callback",[Pvf;Pvargs]))]::accu)letrewrite_toplevel_instr(p,cps_needed,accu)instr=matchinstrwith|Let(x,Apply{f;args;_})whenVar.Set.memxcps_needed->wrap_call~cps_neededpxfargsaccu|Let(x,(Prim(Extern("%resume"|"%perform"|"%reperform"),_)ase))->wrap_primitive~cps_neededpxeaccu|_->p,cps_needed,[instr]::accu(* Wrap function calls inside [caml_callback] at toplevel to avoid
unncessary function nestings. This is not done inside loops since
using repeatedly [caml_callback] can be costly. *)letrewrite_toplevel~cps_neededp=let{start;blocks;_}=pinletcfg=build_graphblocksstartinletidom=dominator_treecfginletfrontiers=dominance_frontiercfgidominletrectraversevisited(p:Code.program)cps_neededin_looppc=ifAddr.Set.mempcvisitedthenvisited,p,cps_neededelseletvisited=Addr.Set.addpcvisitedinletin_loop=current_loop_headerfrontiersin_looppcinletp,cps_needed=ifOption.is_nonein_loopthenletblock=Addr.Map.findpcp.blocksinletp,cps_needed,body_rev=List.fold_left~f:rewrite_toplevel_instr~init:(p,cps_needed,[])block.bodyinletbody=List.concat@@List.revbody_revin{pwithblocks=Addr.Map.addpc{blockwithbody}p.blocks},cps_neededelsep,cps_neededinCode.fold_childrenblockspc(funpc(visited,p,cps_needed)->traversevisitedpcps_neededin_looppc)(visited,p,cps_needed)inlet_,p,cps_needed=traverseAddr.Set.emptypcps_neededNonestartinp,cps_needed(****)letsplit_blocks~cps_needed(p:Code.program)=(* Ensure that function applications and effect primitives are in
tail position *)letsplit_blockpcblockp=letis_split_pointirbranch=matchiwith|Let(x,e)wheneffect_primitive_or_applicatione->((not(empty_bodyr))||matchbranchwith|Branch_->false|Returnx'->not(Var.equalxx')|_->true)&&Var.Set.memxcps_needed|_->falseinletrecsplit(p:Code.program)pcblockacculbranch=matchlwith|[]->letblock={blockwithbody=List.revaccu}in{pwithblocks=Addr.Map.addpcblockp.blocks}|(Let(x,e)asi)::rwhenis_split_pointirbranch->letpc'=p.free_pcinletblock'={params=[];body=[];branch=block.branch}inletblock={blockwithbody=List.rev(Let(x,e)::accu);branch=Branch(pc',[])}inletp={pwithblocks=Addr.Map.addpcblockp.blocks;free_pc=pc'+1}insplitppc'block'[]rbranch|i::r->splitppcblock(i::accu)rbranchinletrecshould_splitlbranch=matchlwith|[]->false|i::r->is_split_pointirbranch||should_splitrbranchinifshould_splitblock.bodyblock.branchthensplitppcblock[]block.bodyblock.branchelsepinAddr.Map.foldsplit_blockp.blocksp(****)letremove_empty_blocks~live_vars(p:Code.program):Code.program=letshortcuts=Hashtbl.create16inletrecresolve_recvisited((pc,args)ascont)=ifAddr.Set.mempcvisitedthencontelsematchHashtbl.find_optshortcutspcwith|Some(params,cont)->letpc',args'=resolve_rec(Addr.Set.addpcvisited)continlets=Subst.from_map(Subst.build_mappingparamsargs)inpc',List.map~f:sargs'|None->continletresolvecont=resolve_recAddr.Set.emptycontinAddr.Map.iter(funpcblock->matchblockwith|{params;body;branch=Branchcont;_}whenempty_bodybody->letargs=List.fold_left~f:(funargsx->Var.Set.addxargs)~init:Var.Set.empty(sndcont)in(* We can skip an empty block if its parameters are only
used as argument to the continuation *)ifList.for_all~f:(funx->live_vars.(Var.idxx)=1&&Var.Set.memxargs)paramsthenHashtbl.addshortcutspc(params,cont)|_->())p.blocks;letblocks=Addr.Map.map(funblock->{blockwithbranch=(letbranch=block.branchinmatchbranchwith|Branchcont->Branch(resolvecont)|Cond(x,cont1,cont2)->Cond(x,resolvecont1,resolvecont2)|Switch(x,a1)->Switch(x,Array.map~f:resolvea1)|Pushtrap(cont1,x,cont2)->Pushtrap(resolvecont1,x,resolvecont2)|Poptrapcont->Poptrap(resolvecont)|Return_|Raise_|Stop->branch)})p.blocksin{pwithblocks}(****)letf~flow_info~live_varsp=lett=Timer.make()inletcps_needed=Partial_cps_analysis.fpflow_infoinletp,cps_needed=ifdouble_translate()then(letp,liftings=Lambda_lifting_simple.f~to_lift:cps_neededpinletcps_needed=Var.Set.map(funf->trySubst.from_mapliftingsfwithNot_found->f)cps_neededinifdebug()then(debug_print"@]";debug_print"@[<v>cps_needed (after lifting) = @[<hov 2>";Var.Set.iter(funv->debug_print"%s,@ "(Var.to_stringv))cps_needed;debug_print"@]@,@]";debug_print"@[<v>After lambda lifting...@,";Code.Print.program(fun__->"")p;debug_print"@]");p,cps_needed)elseletp,cps_needed=rewrite_toplevel~cps_neededpinp,cps_neededinletp=split_blocks~cps_neededpinletp,trampolined_calls,in_cps=cps_transform~live_vars~flow_info~cps_neededpinifDebug.find"times"()thenFormat.eprintf" effects: %a@."Timer.printt;Code.invariantp;ifdebug()then(debug_print"@[<v>After CPS transform:@,";Code.Print.program(fun__->"")p;debug_print"@]");p,trampolined_calls,in_cps