123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241(* 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 code transformation performed to deal with effect handlers produce
deeply nested functions, which is not supported by JavaScript engines.
So, we lift some functions to a lower level to limit the nesting
depth. To lift a function f, we basically wrapped it in a function f'
taking as parameter all free variables of f and returning f. Then we
can move f' to a lower level and replace the definition of f by an
application of f' to the actual value of the free variables. For
instance, this piece of code:
function g () {
var x = e
function f (y) {
return (x + y)
}
...
}
is turned into:
function f'(x) {
return function f (y) {
return (x + y)
}
}
function g () {
var x = e
var f = f'(x)
}
Lambda-lifing has a quadratic space complexity, so we try not to lift
too many functions: we lift functions only starting at a given depth
threshold and when they themselves contains nested functions. We also
only lift functions that are isolated, so as not having to deal with
mutually recursive functions.
This implementation is doing the job: the nesting depth remains low
enough for the JavaScript engines and few functions are lifted.
However, on some large code, we can generate functions with thousands
of parameters. We might be able to improve on that by not lifting
functions too much, so that most of their free variables remain
directly accessible. A complimentary approach would be that when we
lift two functions which are initially within one another, we could
put them into nested wrapper functions. Then, the inner wrapper would
only need to deal with free variables from the inner function which
are not bound in the outer function.
*)open!StdlibopenCodeletdebug=Debug.find"lifting"letbaseline=1(* Depth to which the functions are lifted *)letreccompute_depthprogrampc=Code.preorder_traverse{fold=Code.fold_children}(funpcd->letblock=Code.Addr.Map.findpcprogram.blocksinList.fold_leftblock.body~init:d~f:(fundi->matchiwith|Let(_,Closure(_,(pc',_)))->letd'=compute_depthprogrampc'inmaxd(d'+1)|_->d))pcprogram.blocks0letcollect_free_varsprogramvar_depthdepthpc=letvars=refVar.Set.emptyinletrectraversepc=Code.preorder_traverse{fold=Code.fold_children}(funpc()->letblock=Code.Addr.Map.findpcprogram.blocksinFreevars.iter_block_free_vars(funx->letidx=Var.idxxinifidx<Array.lengthvar_depththen(letd=var_depth.(idx)inassert(d>=0);ifd>baseline&&d<depththenvars:=Var.Set.addx!vars))block;List.iterblock.body~f:(funi->matchiwith|Let(_,Closure(_,(pc',_)))->traversepc'|_->()))pcprogram.blocks()intraversepc;!varsletmark_bound_variablesvar_depthblockdepth=Freevars.iter_block_bound_vars(funx->var_depth.(Var.idxx)<-depth)block;List.iterblock.body~f:(funi->matchiwith|Let(_,Closure(params,_))->List.iterparams~f:(funx->var_depth.(Var.idxx)<-depth+1)|_->())letrectraversevar_depth(program,functions)pcdepthlimit=Code.preorder_traverse{fold=Code.fold_children}(funpc(program,functions)->letblock=Code.Addr.Map.findpcprogram.blocksinmark_bound_variablesvar_depthblockdepth;ifdepth=baselinethen(assert(List.is_emptyfunctions);letprogram,body=List.fold_rightblock.body~init:(program,[])~f:(funi(program,rem)->matchiwith|Let(_,Closure(_,(pc',_)))asi->letprogram,functions=traversevar_depth(program,[])pc'(depth+1)limitinprogram,List.rev_appendfunctions(i::rem)|i->program,i::rem)in{programwithblocks=Addr.Map.addpc{blockwithbody}program.blocks},[])elseifdepth<limitthenList.fold_leftblock.body~init:(program,functions)~f:(funsti->matchiwith|Let(_,Closure(_,(pc',_)))->traversevar_depthstpc'(depth+1)limit|_->st)else(* We lift isolated closures (so that we do not have to deal
with mutual recursion) which are deep enough and contain
deeply nested closures. *)letdoes_not_start_with_closurel=matchlwith|Let(_,Closure_)::_->false|_->trueinletrecrewrite_bodyfirststl=matchlwith|(Let(f,(Closure(_,(pc',_))ascl))asi)::remwhenfirst&&does_not_start_with_closurerem->letthreshold=Config.Param.lambda_lifting_threshold()inletprogram,functions=traversevar_depthstpc'(depth+1)(depth+threshold)inifcompute_depthprogrampc'+1>=thresholdthen(letfree_vars=collect_free_varsprogramvar_depth(depth+1)pc'inlets=Var.Set.fold(funxm->Var.Map.addx(Var.forkx)m)free_varsVar.Map.emptyinletprogram=Subst.cont(Subst.from_maps)pc'programinletf'=tryVar.Map.findfswithNot_found->Var.forkfinlets=Var.Map.bindings(Var.Map.removefs)inletf''=Var.forkfinifdebug()thenFormat.eprintf"LIFT %s (depth:%d free_vars:%d inner_depth:%d)@."(Code.Var.to_stringf'')depth(Var.Set.cardinalfree_vars)(compute_depthprogrampc');letpc''=program.free_pcinletbl={params=[];body=[Let(f',cl)];branch=Returnf'}inletprogram={programwithfree_pc=pc''+1;blocks=Addr.Map.addpc''blprogram.blocks}inletfunctions=Let(f'',Closure(List.maps~f:snd,(pc'',[])))::functionsinletrem',st=rewrite_bodyfalse(program,functions)reminassert((not(List.is_emptyrem'))||matchblock.branchwith|Return_->false|_->true);(Let(f,Apply{f=f'';args=List.map~f:fsts;exact=true})::rem',st))elseletrem',st=rewrite_bodyfalse(program,functions)remini::rem',st|(Let(_,Closure(_,(pc',_)))asi)::rem->letst=traversevar_depthstpc'(depth+1)limitinletrem',st=rewrite_bodyfalsestremini::rem',st|i::rem->letrem',st=rewrite_body(does_not_start_with_closurel)stremini::rem',st|[]->[],stinletbody,(program,functions)=rewrite_bodytrue(program,functions)block.bodyin({programwithblocks=Addr.Map.addpc{blockwithbody}program.blocks},functions))pcprogram.blocks(program,functions)letfprogram=lett=Timer.make()inletnv=Var.count()inletvar_depth=Array.makenv(-1)inletprogram,functions=letthreshold=Config.Param.lambda_lifting_threshold()intraversevar_depth(program,[])program.start0(baseline+threshold)inassert(List.is_emptyfunctions);ifDebug.find"times"()thenFormat.eprintf" lambda lifting: %a@."Timer.printt;program