123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415(* 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]).
We rely on inlining to eliminate some administrative redexes.
*)open!StdlibopenCodetypegraph={succs:(Addr.t,Addr.Set.t)Hashtbl.t;exn_handlers:(Addr.t,unit)Hashtbl.t;reverse_post_order:Addr.tlist}letbuild_graphblockspc=letsuccs=Hashtbl.create16inletexn_handlers=Hashtbl.create16inletl=ref[]inletvisited=Hashtbl.create16inletrectraversepc=ifnot(Hashtbl.memvisitedpc)then(Hashtbl.addvisitedpc();letsuccessors=Code.fold_childrenblockspcAddr.Set.addAddr.Set.emptyin(match(Addr.Map.findpcblocks).branchwith|Pushtrap(_,_,(pc',_),_)->Hashtbl.addexn_handlerspc'()|_->());Hashtbl.addsuccspcsuccessors;Addr.Set.itertraversesuccessors;l:=pc::!l)intraversepc;{succs;exn_handlers;reverse_post_order=!l}letdominator_treeg=(* A Simple, Fast Dominance Algorithm
Keith D. Cooper, Timothy J. Harvey, and Ken Kennedy *)letdom=Hashtbl.create16inletorder=Hashtbl.create16inList.iterig.reverse_post_order~f:(funipc->Hashtbl.addorderpci);letrecinterpcpc'=(* Compute closest common ancestor *)ifpc=pc'thenpcelseifHashtbl.findorderpc<Hashtbl.findorderpc'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(* 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. 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_closuresgidom:jump_closures=Hashtbl.fold(funnodeidom_nodejc->ifHashtbl.memg.exn_handlersnodethenjcelseletcname=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}typest={mutablenew_blocks:Code.blockAddr.Map.t*Code.Addr.t;blocks:Code.blockAddr.Map.t;jc:jump_closures;closure_continuation:Addr.t->Var.t}letadd_blockstblock=letblocks,free_pc=st.new_blocksinst.new_blocks<-Addr.Map.addfree_pcblockblocks,free_pc+1;free_pcletclosure_of_pc~stpc=tryAddr.Map.findpcst.jc.closure_of_jumpwithNot_found->assertfalseletallocate_closure~st~params~body~branch=letblock={params=[];body;branch}inletpc=add_blockstblockinletname=Var.fresh()in[Let(name,Closure(params,(pc,[])))],nameletcps_branch~st(pc,args)=letret=Var.fresh()in[Let(ret,Apply{f=closure_of_pc~stpc;args;exact=true})],Returnretletcps_jump_cont~stcont=letcall_block=letbody,branch=cps_branch~stcontinadd_blockst{params=[];body;branch}incall_block,[]letcps_last~st(last:last)~k:instrlist*last=matchlastwith|Returnx->letret=Var.fresh()in[Let(ret,Apply{f=k;args=[x];exact=true})],Returnret|Raise(x,_)->letret=Var.fresh()inletexn_handler=Var.fresh_n"raise"in([Let(exn_handler,Prim(Extern"caml_pop_trap",[]));Let(ret,Apply{f=exn_handler;args=[x];exact=true})],Returnret)|Stop->[],Stop|Branchcont->cps_branch~stcont|Cond(x,cont1,cont2)->[],Cond(x,cps_jump_cont~stcont1,cps_jump_cont~stcont2)|Switch(x,c1,c2)->(* To avoid code duplication during JavaScript generation, we need
to create a single block per continuation *)letcps_jump_cont=Fun.memoize(cps_jump_cont~st)in[],Switch(x,Array.mapc1~f:cps_jump_cont,Array.mapc2~f:cps_jump_cont)|Pushtrap((pc,args),x,handler_cont,_)->letconstr_handler,exn_handler=(* Construct handler closure *)allocate_closure~st~params:[x]~body:[]~branch:(Branchhandler_cont)inletret=Var.fresh()in(constr_handler@[Let(Var.fresh(),Prim(Extern"caml_push_trap",[Pvexn_handler]));Let(ret,Apply{f=closure_of_pc~stpc;args;exact=true})],Returnret)|Poptrap(pc,args)->letret=Var.fresh()inletexn_handler=Var.fresh()in([Let(exn_handler,Prim(Extern"caml_pop_trap",[]));Let(ret,Apply{f=closure_of_pc~stpc;args;exact=true})],Returnret)letcps_instr~st(instr:instr):instr=matchinstrwith|Let(x,Closure(params,(pc,args)))->Let(x,Closure(params@[st.closure_continuationpc],(pc,args)))|Let(x,Prim(Extern"caml_alloc_dummy_function",[size;arity]))->(matcharitywith|Pc(Inta)->Let(x,Prim(Extern"caml_alloc_dummy_function",[size;Pc(Int(Int32.succa))]))|_->assertfalse)|Let(_,(Apply_|Prim(Extern("%resume"|"%perform"|"%reperform"),_)))->assertfalse|_->instrletcps_block~st~kpcblock=letalloc_jump_closures=matchAddr.Map.findpcst.jc.closures_of_alloc_sitewith|to_allocate->List.mapto_allocate~f:(fun(cname,jump_pc)->letjump_block=Addr.Map.findjump_pcst.blocksinletfresh_params=List.mapjump_block.params~f:(fun_->Var.fresh())inLet(cname,Closure(fresh_params,(jump_pc,fresh_params))))|exceptionNot_found->[]inletrewrite_instre=matchewith|Apply{f;args;exact}->Some(fun~x~k->[Let(x,Apply{f;args=args@[k];exact})])|Prim(Extern"%resume",[Pvstack;Pvf;Pvarg])->Some(fun~x~k->letk'=Var.fresh_n"cont"in[Let(k',Prim(Extern"caml_resume_stack",[Pvstack;Pvk]));Let(x,Apply{f;args=[arg;k'];exact=false})])|Prim(Extern"%perform",[Pveffect])->Some(fun~x~k->[Let(x,Prim(Extern"caml_perform_effect",[Pveffect;Pc(Int0l);Pvk]))])|Prim(Extern"%reperform",[Pveff;Pvcontinuation])->Some(fun~x~k->[Let(x,Prim(Extern"caml_perform_effect",[Pveff;Pvcontinuation;Pvk]))])|_->Noneinletrewritten_block=matchList.split_lastblock.body,block.branchwith|Some(body_prefix,Let(x,e)),Returnret->Option.map(rewrite_instre)~f:(funinstrs->assert(List.is_emptyalloc_jump_closures);assert(Var.equalxret);body_prefix,instrs~x~k,block.branch)|Some(body_prefix,Let(x,e)),Branchcont->letallocate_continuationf=letconstr_cont,k'=(* Construct continuation: it binds the return value [x],
allocates closures for dominated blocks and jumps to the
next block. *)letpc,args=continletret=Var.fresh()inletf'=closure_of_pc~stpcinallocate_closure~st~params:[x]~body:(alloc_jump_closures@[Let(ret,Apply{f=f';args;exact=true})])~branch:(Returnret)inletret=Var.fresh()inbody_prefix,constr_cont@f~x:ret~k:k',ReturnretinOption.map(rewrite_instre)~f:allocate_continuation|Some(_,(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)->List.mapbody_prefix~f:(funi->cps_instr~sti)@last_instrs,last|None->letlast_instrs,last=cps_last~stblock.branch~kinletbody=List.mapblock.body~f:(funi->cps_instr~sti)@alloc_jump_closures@last_instrsinbody,lastin{params=block.params;body;branch=last}letsplit_blocks(p:Code.program)=(* Ensure that function applications and effect primitives are in
tail position *)letsplit_blockpcblockp=letis_split_pointirbranch=matchiwith|Let(x,(Apply_|Prim(Extern("%resume"|"%perform"|"%reperform"),_)))->((not(List.is_emptyr))||matchbranchwith|Branch_->false|Returnx'->not(Var.equalxx')|_->true)|_->falseinletrecsplit(p:Code.program)pcblockacculbranch=matchlwith|[]->letblock={blockwithbody=List.revaccu}in{pwithblocks=Addr.Map.addpcblockp.blocks}|(Let(x',e)asi)::rwhenis_split_pointirbranch->letx=Var.forkx'inletpc'=p.free_pcinletblock'={params=[x'];body=[];branch=block.branch}inletblock={blockwithbody=List.rev(Let(x,e)::accu);branch=Branch(pc',[x])}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.blockspletf(p:Code.program)=letp=split_blockspinletclosure_continuation=(* Provide a name for the continuation of a closure (before CPS
transform), which can be referred from all the blocks it contains *)lettbl=Hashtbl.create4infunpc->tryHashtbl.findtblpcwithNot_found->letk=Var.fresh_n"cont"inHashtbl.addtblpck;kinletp=Code.fold_closuresp(fun__(start,_)({blocks;free_pc;_}asp)->letcfg=build_graphblocksstartinletidom=dominator_treecfginletclosure_jc=jump_closurescfgidominletst={new_blocks=Addr.Map.empty,free_pc;blocks;jc=closure_jc;closure_continuation}inletk=closure_continuationstartinletblocks=Code.traverse{fold=Code.fold_children}(funpcblocks->Addr.Map.addpc(cps_block~st~kpc(Addr.Map.findpcblocks))blocks)startst.blocksst.blocksinletnew_blocks,free_pc=st.new_blocksinletblocks=Addr.Map.foldAddr.Map.addnew_blocksblocksin{pwithblocks;free_pc})pin(* 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([closure_continuationp.start],(p.start,[])));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}letfp=lett=Timer.make()inletr=fpinifDebug.find"times"()thenFormat.eprintf" effects: %a@."Timer.printt;r