123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739(* 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 goal of the analysis is to get a good idea of which function might
be called where, and of which functions might be called from some
unknown location (which function 'escapes'). We also keep track of
blocks, to track functions across modules.
*)open!Stdlibletdebug=Debug.find"global-flow"lettimes=Debug.find"times"openCode(****)(* Compute the list of variables containing the return values of each
function *)letreturn_valuesp=Code.fold_closuresp(funname_opt_(pc,_)rets->matchname_optwith|None->rets|Somename->lets=Code.traverse{fold=fold_children}(funpcs->letblock=Addr.Map.findpcp.blocksinmatchblock.branchwith|Returnx->Var.Set.addxs|_->s)pcp.blocksVar.Set.emptyinVar.Map.addnamesrets)Var.Map.empty(****)(* A variable is either let-bound, or a parameter, to which we
associate a set of possible arguments.
*)typedef=|ExprofCode.expr|Phiof{known:Var.Set.t(* Known arguments *);others:bool(* Can there be other arguments *)}letundefined=Phi{known=Var.Set.empty;others=false}letis_undefinedd=matchdwith|Expr_->false|Phi{known;others}->Var.Set.is_emptyknown&¬otherstypeescape_status=|Escape|Escape_constant(* Escapes but we know the value is not modified *)|Notypestate={vars:Var.ISet.t(* Set of all veriables considered *);deps:Var.tVar.Tbl.DataSet.tVar.Tbl.t(* Dependency between variables *);defs:defarray(* Definition of each variable *);variable_may_escape:escape_statusarray(* Any value bound to this variable may escape *);variable_possibly_mutable:Var.ISet.t(* Any value bound to this variable may be mutable *);may_escape:escape_statusarray(* This value may escape *);possibly_mutable:Var.ISet.t(* This value may be mutable *);return_values:Var.Set.tVar.Map.t(* Set of variables holding return values of each function *);known_cases:(Var.t,intlist)Hashtbl.t(* Possible tags for a block after a [switch]. This is used to
get a more precise approximation of the effect of a field
access [Field] *);applied_functions:(Var.t*Var.t,unit)Hashtbl.t(* Functions that have been already considered at a call site.
This is to avoid repeated computations *);fast:bool}letadd_varstx=Var.ISet.addst.varsx(* x depends on y *)letadd_depstxy=Var.Tbl.add_setst.depsyxletadd_expr_defstxe=add_varstx;letidx=Var.idxxinassert(is_undefinedst.defs.(idx));st.defs.(idx)<-Expreletadd_assign_defstxy=add_varstx;add_depstxy;letidx=Var.idxxinmatchst.defs.(idx)with|Expr_->assertfalse|Phi{known;others}->st.defs.(idx)<-Phi{known=Var.Set.addyknown;others}letadd_param_defstx=add_varstx;letidx=Var.idxxinassert(is_undefinedst.defs.(idx));ifst.fastthenst.defs.(idx)<-Phi{known=Var.Set.empty;others=true}letrecarg_depsst?ignoreparamsargs=matchparams,argswith|x::params,y::args->(* This is to deal with the [else] clause of a conditional,
where we know that the value of the tested variable is 0. *)(matchignorewith|Somey'whenVar.equalyy'->()|_->add_assign_defstxy);arg_depsstparamsargs|[],[]->()|_->assertfalseletcont_depsblocksst?ignore(pc,args)=letblock=Addr.Map.findpcblocksinarg_depsst?ignoreblock.paramsargsletdo_escapestlevelx=st.variable_may_escape.(Var.idxx)<-levelletpossibly_mutablestx=Var.ISet.addst.variable_possibly_mutablexletexpr_depsblocksstxe=matchewith|Constant_|Prim((Vectlength|Not|IsInt|Eq|Neq|Lt|Le|Ult),_)|Block_->()|Special_->()|Prim((Extern("caml_check_bound"|"caml_check_bound_float"|"caml_check_bound_gen"|"caml_array_unsafe_get"|"caml_floatarray_unsafe_get")|Array_get),l)->(* The analysis knowns about these primitives, and will compute
an approximation of the value they return based on an
approximation of their arguments *)(ifst.fastthenmatchlwith|Pvx::_->do_escapestEscapex|Pc_::_->()|[]->assertfalse);List.iter~f:(funa->matchawith|Pc_->()|Pvy->add_depstxy)l|Prim(Externname,l)->(* Set the escape status of the arguments *)letka=matchPrimitive.kind_argsnamewith|Somel->l|None->(matchPrimitive.kindnamewith|`Mutable|`Mutator->[]|`Pure->List.mapl~f:(fun_->`Const))inletrecloopargska=matchargs,kawith|[],_->()|Pc_::ax,[]->loopax[]|Pva::ax,[]->do_escapestEscapea;loopax[]|a::ax,k::kx->(matcha,kwith|Pc_,_->()|Pvv,`Const->do_escapestEscape_constantv|Pvv,`Shallow_const->(matchst.defs.(Var.idxv)with|Expr(Block(_,a,_,_))->Array.itera~f:(funx->do_escapestEscapex)|_->do_escapestEscapev)|Pvv,`Object_literal->(matchst.defs.(Var.idxv)with|Expr(Block(_,a,_,_))->Array.itera~f:(funx->matchst.defs.(Var.idxx)with|Expr(Block(_,[|_k;v|],_,_))->do_escapestEscapev|_->do_escapestEscapex)|_->do_escapestEscapev)|Pvv,`Mutable->do_escapestEscapev);loopaxkxinlooplka|Apply{f;args;_}->(add_depstxf;(* If [f] is obviously a function, we can add appropriate
dependencies right now. This speeds up the analysis
significantly. *)matchst.defs.(Var.idxf)with|Expr(Closure(params,_))whenList.lengthargs=List.lengthparams->Hashtbl.addst.applied_functions(x,f)();ifst.fastthenList.iter~f:(funa->do_escapestEscapea)argselseList.iter2~f:(funpa->add_assign_defstpa)paramsargs;Var.Set.iter(funy->add_depstxy)(Var.Map.findfst.return_values)|_->())|Closure(l,cont)->List.iterl~f:(funx->add_param_defstx);cont_depsblocksstcont|Field(y,_,_)->add_depstxyletprogram_depsst{start;blocks;_}=Code.traverse{Code.fold=Code.fold_children}(funpc()->matchAddr.Map.findpcblockswith|{branch=Returnx;_}->do_escapestEscapex|_->())startblocks();Addr.Map.iter(fun_block->List.iterblock.body~f:(funi->matchiwith|Let(x,e)->add_expr_defstxe;expr_depsblocksstxe|Assign(x,y)->add_assign_defstxy|Set_field(x,_,_,y)|Array_set(x,_,y)->possibly_mutablestx;do_escapestEscapey|Event_|Offset_ref_->());matchblock.branchwith|Return_|Stop->()|Raise(x,_)->do_escapestEscapex|Branchcont|Poptrapcont->cont_depsblocksstcont|Cond(x,cont1,cont2)->cont_depsblocksstcont1;cont_depsblocksst~ignore:xcont2|Switch(x,a1)->(Array.itera1~f:(funcont->cont_depsblocksstcont);ifnotst.fastthen(* looking up the def of x is fine here, because the tag
we're looking for is at addr [pc - 2] (see
parse_bytecode.ml) and [Addr.Map.iter] iterate in
increasing order *)matchst.defs.(Code.Var.idxx)with|Expr(Prim(Extern"%direct_obj_tag",[Pvb]))->leth=Hashtbl.create16inArray.iteria1~f:(funi(pc,_)->Hashtbl.replacehpc(i::(tryHashtbl.findhpcwithNot_found->[])));Hashtbl.iter(funpctags->letblock=Addr.Map.findpcblocksinList.iter~f:(funi->matchiwith|Let(y,Field(x',_,_))whenVar.equalbx'->Hashtbl.addst.known_casesytags|_->())block.body)h|Expr_|Phi_->())|Pushtrap(cont,x,cont_h)->add_varstx;st.defs.(Var.idxx)<-Phi{known=Var.Set.empty;others=true};cont_depsblocksstcont_h;cont_depsblocksstcont)blocks(* For each variable, we keep track of which values, function or
block, it may contain. Other kinds of values are not relevant and
just ignored. We loose a lot of information when going to [Top]
since we have to assume that all functions might escape. So, having
possibly unknown values does not move us to [Top]; we use a flag
for that instead. *)typeapprox=|Top|Valuesof{known:Var.Set.t(* List of possible values (functions and blocks) *);others:bool(* Whether other functions or blocks are possible *)}moduleDomain=structtypet=approxletbot=Values{known=Var.Set.empty;others=false}letothers=Values{known=Var.Set.empty;others=true}letsingletonx=Values{known=Var.Set.singletonx;others=false}letequalxy=matchx,ywith|Top,Top->true|Values{known;others},Values{known=known';others=others'}->Var.Set.equalknownknown'&&Bool.equalothersothers'|Top,Values_|Values_,Top->falselethigher_escape_statusss'=matchs,s'with|Escape,Escape->false|Escape,(Escape_constant|No)->true|Escape_constant,(Escape|Escape_constant)->false|Escape_constant,No->true|No,(Escape|Escape_constant|No)->falseletrecvalue_escape~update~st~approxsx=letidx=Var.idxxinifhigher_escape_statussst.may_escape.(idx)then(st.may_escape.(idx)<-s;matchst.defs.(idx)with|Expr(Block(_,a,_,mut))->(Array.iter~f:(funy->variable_escape~update~st~approxsy)a;matchs,mutwith|Escape,Maybe_mutable->Var.ISet.addst.possibly_mutablex;update~children:truex|(Escape_constant|No),_|Escape,Immutable->())|Expr(Closure(params,_))->List.iter~f:(funy->(matchst.defs.(Var.idxy)with|Phi{known;_}->st.defs.(Var.idxy)<-Phi{known;others=true}|Expr_->assertfalse);update~children:falsey)params;Var.Set.iter(funy->variable_escape~update~st~approxsy)(Var.Map.findxst.return_values)|_->())andvariable_escape~update~st~approxsx=ifhigher_escape_statussst.variable_may_escape.(Var.idxx)then(st.variable_may_escape.(Var.idxx)<-s;approx_escape~update~st~approxs(Var.Tbl.getapproxx))andapprox_escape~update~st~approxsa=matchawith|Top->()|Values{known;_}->Var.Set.iter(funx->value_escape~update~st~approxsx)knownletjoin~update~st~approxxy=matchx,ywith|Top,_->approx_escape~update~st~approxEscapey;Top|_,Top->approx_escape~update~st~approxEscapex;Top|Values{known;others},Values{known=known';others=others'}->Values{known=Var.Set.unionknownknown';others=others||others'}letjoin_set~update~st~approx?others:(o=false)fs=Var.Set.fold(funxa->join~update~st~approx(fx)a)s(ifothenotherselsebot)letmark_mutable~update~sta=matchawith|Top->()|Values{known;_}->Var.Set.iter(funx->matchst.defs.(Var.idxx)with|Expr(Block(_,_,_,Maybe_mutable))->ifnot(Var.ISet.memst.possibly_mutablex)then(Var.ISet.addst.possibly_mutablex;update~children:truex)|Expr(Block(_,_,_,Immutable))|Expr(Closure_)->()|Phi_|Expr_->assertfalse)knownendletpropagatest~updateapproxx=matchst.defs.(Var.idxx)with|Phi{known;others}->Domain.join_set~update~st~approx~others(funy->Var.Tbl.getapproxy)known|Expre->(matchewith|Constant_->(* A constant cannot contain a function *)Domain.bot|Closure_|Block_->Domain.singletonx|Field(y,n,_)->(matchVar.Tbl.getapproxywith|Values{known;others}->lettags=trySome(Hashtbl.findst.known_casesx)withNot_found->NoneinDomain.join_set~others~update~st~approx(funz->matchst.defs.(Var.idxz)with|Expr(Block(t,a,_,_))whenn<Array.lengtha&&matchtagswith|Sometags->List.memqt~set:tags|None->true->lett=a.(n)inletm=Var.ISet.memst.possibly_mutablezinifnotmthenadd_depstxz;add_depstxt;leta=Var.Tbl.getapproxtinifmthenDomain.join~update~st~approxDomain.othersaelsea|Expr(Block_|Closure_)->Domain.bot|Phi_|Expr_->assertfalse)known|Top->Top)|Prim(Extern("caml_check_bound"|"caml_check_bound_float"|"caml_check_bound_gen"),[Pvy;_])->Var.Tbl.getapproxy|Prim((Array_get|Extern("caml_array_unsafe_get"|"caml_floatarray_unsafe_get")),[Pvy;_])->(ifst.fastthenDomain.otherselsematchVar.Tbl.getapproxywith|Values{known;others}->Domain.join_set~update~st~approx~others(funz->matchst.defs.(Var.idxz)with|Expr(Block(_,lst,_,_))->letm=Var.ISet.memst.possibly_mutablezinifnotmthenadd_depstxz;Array.iter~f:(funt->add_depstxt)lst;leta=Array.fold_left~f:(funacct->Domain.join~update~st~approx(Var.Tbl.getapproxt)acc)~init:Domain.botlstinifmthenDomain.join~update~st~approxDomain.othersaelsea|Expr(Closure_)->Domain.bot|Phi_|Expr_->assertfalse)known|Top->Top)|Prim(Array_get,_)->Domain.others|Prim((Vectlength|Not|IsInt|Eq|Neq|Lt|Le|Ult),_)->(* The result of these primitive is neither a function nor a
block *)Domain.bot|Prim(Extern_,_)->Domain.others|Special_->Domain.others|Apply{f;args;_}->(matchVar.Tbl.getapproxfwith|Values{known;others}->ifothersthenList.iter~f:(funy->Domain.variable_escape~update~st~approxEscapey)args;Domain.join_set~update~st~approx~others(fung->matchst.defs.(Var.idxg)with|Expr(Closure(params,_))whenList.lengthargs=List.lengthparams->ifnot(Hashtbl.memst.applied_functions(x,g))then(Hashtbl.addst.applied_functions(x,g)();ifst.fastthenList.iter~f:(funy->Domain.variable_escape~update~st~approxEscapey)argselseList.iter2~f:(funpa->add_assign_defstpa;update~children:falsep)paramsargs;Var.Set.iter(funy->add_depstxy)(Var.Map.findgst.return_values));Domain.join_set~update~st~approx(funy->Var.Tbl.getapproxy)(Var.Map.findgst.return_values)|Expr(Closure(_,_))->(* The funciton is partially applied or over applied *)List.iter~f:(funy->Domain.variable_escape~update~st~approxEscapey)args;Domain.variable_escape~update~st~approxEscapeg;Domain.others|Expr(Block_)->Domain.bot|Phi_|Expr_->assertfalse)known|Top->List.iter~f:(funy->Domain.variable_escape~update~st~approxEscapey)args;Top))letpropagatest~updateapproxx=letres=propagatest~updateapproxxinmatchreswith|Values{known;_}whenVar.Set.cardinalknown>=200->(* When the set of possible values get to large, we give up and
just forget about it. This is crucial to make the analysis
terminates in a reasonable amount of time. This happens when
our analysis is very imprecise (for instance, with
[List.map]), so we may not loose too much by doing that. *)ifdebug()thenFormat.eprintf"TOP %a@."Var.printx;Domain.approx_escape~update~st~approxEscaperes;Top|Values_->(matchst.variable_may_escape.(Var.idxx)with|(Escape|Escape_constant)ass->Domain.approx_escape~update~st~approxsres|No->());ifVar.ISet.memst.variable_possibly_mutablexthenDomain.mark_mutable~update~stres;res|Top->TopmoduleG=Dgraph.Make_Imperative(Var)(Var.ISet)(Var.Tbl)moduleSolver=G.Solver(Domain)letprint_approxstfa=matchawith|Top->Format.fprintff"top"|Values{known;others}->Format.fprintff"{%a/%b}"(Format.pp_print_list~pp_sep:(funf()->Format.fprintff", ")(funfx->Format.fprintff"%a(%s)"Var.printx(matchst.defs.(Var.idxx)with|Expr(Closure_)->"C"|Expr(Block_)->("B"^matchst.may_escape.(Var.idxx)with|Escape->"X"|_->"")|_->"O")))(Var.Set.elementsknown)othersletsolverst=letg={G.domain=st.vars;G.iter_children=(funfx->Var.Tbl.DataSet.iter(funk->fk)(Var.Tbl.getst.depsx))}inletres=Solver.f'()g(propagatest)inifdebug()thenSolver.checkgres(propagatest)(funxab->Format.eprintf"Incorrect value: %a: %a -> %a@."Var.printx(print_approxst)a(print_approxst)b);res(****)typeinfo={info_defs:defarray;info_approximation:Domain.tVar.Tbl.t;info_may_escape:Var.ISet.t;info_variable_may_escape:escape_statusarray;info_return_vals:Var.Set.tVar.Map.t}letf~fastp=lett=Timer.make()inlett1=Timer.make()inletrets=return_valuespinletnv=Var.count()inletvars=Var.ISet.empty()inletdeps=Var.Tbl.make_set()inletdefs=Array.makenvundefinedinletvariable_may_escape=Array.makenvNoinletvariable_possibly_mutable=Var.ISet.empty()inletmay_escape=Array.makenvNoinletpossibly_mutable=Var.ISet.empty()inletst={vars;deps;defs;return_values=rets;variable_may_escape;variable_possibly_mutable;may_escape;possibly_mutable;known_cases=Hashtbl.create16;applied_functions=Hashtbl.create16;fast}inprogram_depsstp;iftimes()thenFormat.eprintf" global flow analysis (initialize): %a@."Timer.printt1;lett2=Timer.make()inletapproximation=solverstiniftimes()thenFormat.eprintf" global flow analysis (solve): %a@."Timer.printt2;iftimes()thenFormat.eprintf" global flow analysis: %a@."Timer.printt;ifdebug()thenVar.ISet.iter(funx->lets=Var.Tbl.getapproximationxinifnot(Domain.equalsDomain.bot)thenFormat.eprintf"%a: %a@."Var.printx(funfa->matchawith|Top->Format.fprintff"top"|Values_->Format.fprintff"%a mut:%b vmut:%b vesc:%s esc:%s"(print_approxst)a(Var.ISet.memst.possibly_mutablex)(Var.ISet.memst.variable_possibly_mutablex)(matchst.variable_may_escape.(Var.idxx)with|Escape->"Y"|Escape_constant->"y"|No->"n")(matchst.may_escape.(Var.idxx)with|Escape->"Y"|Escape_constant->"y"|No->"n"))s)vars;letinfo_variable_may_escape=variable_may_escapeinletinfo_may_escape=Var.ISet.empty()inArray.iteri~f:(funis->ifPoly.(s<>No)thenVar.ISet.addinfo_may_escape(Var.of_idxi))may_escape;{info_defs=defs;info_approximation=approximation;info_variable_may_escape;info_may_escape;info_return_vals=rets}letexact_callinfofn=matchVar.Tbl.getinfo.info_approximationfwith|Top|Values{others=true;_}->false|Values{known;others=false}->Var.Set.for_all(fung->matchinfo.info_defs.(Var.idxg)with|Expr(Closure(params,_))->List.lengthparams=n|Expr(Block_)->true|Expr_|Phi_->assertfalse)knownletfunction_arityinfof=matchVar.Tbl.getinfo.info_approximationfwith|Top|Values{others=true;_}->None|Values{known;others=false}->(matchVar.Set.fold(fungacc->matchinfo.info_defs.(Var.idxg)with|Expr(Closure(params,_))->(letn=List.lengthparamsinmatchaccwith|None->Some(Somen)|Some(Somen')whenn<>n'->SomeNone|Some_->acc)|Expr(Block_)->acc|Expr_|Phi_->assertfalse)knownNonewith|Somev->v|None->None)