123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386(* Js_of_ocaml compiler
* http://www.ocsigen.org/js_of_ocaml/
* Copyright (C) 2013 Jérôme Vouillon
* Copyright (C) 2013 Hugo Heuzard
*
* 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.
*)open!StdlibopenJavascriptletdebug=Debug.find"shortvar"moduleS=Code.Var.SetmoduleVar=Code.VarmoduletypeStrategy=sigtypetvalcreate:int->tvalrecord_block:t->Js_traverse.t->catch:bool->Javascript.identlist->unitvalallocate_variables:t->count:intJavascript.IdentMap.t->stringarrayendmoduleMin:Strategy=struct(*
We are trying to achieve the following goals:
(1) variable names should be as short as possible
(2) one should reuse as much as possible a small subsets of variable
names
(3) function parameters should be: function(a,b,...){...}
(4) for longer variable names, variable which are closed from one
another should share a same prefix
Point (1) minimizes the size of uncompressed files, while point (2) to
(4) improve compression.
We use the following strategy. We maintain the constraint that
variables occurring in a function should keep different names.
We first assign names a, b, ... (in order) to function parameters,
starting from inner functions, skipping variables which have a
conflict with a previously names variable (goal 3). Then, we order
the remaining variables by their number of occurrences, then by
their index (goal 4), and greedily assigned name to them. For that,
we use for each variable the smallest possible name still available
(goal 1/2).
This algorithm seems effective. Here are some statistics gathered
while compiling the OCaml toplevel:
(1) We get 132025 occurrences of one-char variables out of 169728
occurrences while the optimal number (determined using a mixed
integer linear programming solver) is 132105 occurrences (80 more
occurrences).
(2) Variable names are heavily biased toward character a: among
variables, we have about 34000 occurrences of character a, less
than 5000 occurrences of character i (9th character, out of the 54
characters that can start an identifier), and about 1500
occurrences of character A.
(3) About 6% of the function parameters are not assigned as wanted;
it is not clear we can do any better: there are a lot of nested
functions.
(4) We save 8181 bytes on the compressed file (1.8%) by sorting
variables using their index as a secondary key rather that just
based on their weights (the size of the uncompressed file remains
unchanged)
*)typealloc={mutablefirst_free:int;mutableused:BitSet.t}letmake_alloc_table()={first_free=0;used=BitSet.create()}letnext_availableai=BitSet.next_freea.used(maxia.first_free)letallocateai=BitSet.seta.usedi;ifa.first_free=ithena.first_free<-BitSet.next_freea.useda.first_freeletis_availableli=List.for_alll~f:(funa->BitSet.mema.usedi)letfirst_availablel=letrecfind_recnl=letn'=List.fold_leftl~init:n~f:(funna->next_availablean)inifn=n'thennelsefind_recn'linfind_rec0lletmark_allocatedli=List.iterl~f:(funa->allocateai)typet={constr:alloclistarray;(* Constraints on variables *)mutableparameters:Var.tlistarray;(* Function parameters *)mutableconstraints:S.tlist}(* For debugging *)letcreatenv={constr=Array.makenv[];parameters=[|[]|];constraints=[]}(* let output_debug_information t count =
*
*
* let weight v = (IdentMap.find (V v) count) in
*
* let usage =
* List.fold_left
* (fun u s ->
* S.fold
* (fun v u -> VM.add v (try 1 + VM.find v u with Not_found -> 1) u)
* s u)
* VM.empty t.constraints
* in
*
* let l = List.map fst (VM.bindings usage) in
*
* let ch = open_out "/tmp/weights.txt" in
* List.iter
* (fun v ->
* Printf.fprintf ch "%d / %d / %d\n" (weight v)
* (VM.find v usage) (Var.idx v))
* l;
* close_out ch;
*
* let ch = open_out "/tmp/problem.txt" in
* Printf.fprintf ch "Maximize\n";
* let a = Array.of_list l in
* Printf.fprintf ch " ";
* for i = 0 to Array.length a - 1 do
* let v = a.(i) in
* let w = weight v in
* if i > 0 then Printf.fprintf ch " + ";
* Printf.fprintf ch "%d x%d" w (Var.idx v)
* done;
* Printf.fprintf ch "\n";
* Printf.fprintf ch "Subject To\n";
* List.iter
* (fun s ->
* if S.cardinal s > 0 then begin
* Printf.fprintf ch " ";
* let a = Array.of_list (S.elements s) in
* for i = 0 to Array.length a - 1 do
* if i > 0 then Printf.fprintf ch " + ";
* Printf.fprintf ch "x%d" (Var.idx a.(i))
* done;
* Printf.fprintf ch "<= 54\n"
* end)
* t.constraints;
* Printf.fprintf ch "Binary\n ";
* List.iter (fun v -> Printf.fprintf ch " x%d" (Var.idx v)) l;
* Printf.fprintf ch "\nEnd\n";
* close_out ch;
*
* let ch = open_out "/tmp/problem2" in
* let var x = string_of_int (Var.idx x) in
* let a = List.map (fun v -> (var v, weight v)) l in
* let b =
* List.map (fun s -> List.map var (S.elements s)) t.constraints in
* let c = List.map var l in
* output_value ch
* ((a, b, c) : (string * int) list * string list list * string list);
* close_out ch *)letallocate_variablest~count=letweightv=tryIdentMap.find(V(Var.of_idxv))countwithNot_found->0inletconstr=t.constrinletlen=Array.lengthconstrinletidx=Array.makelen0infori=0tolen-1doidx.(i)<-idone;Array.stable_sortidx~cmp:(funij->compare(weightj)(weighti));letname=Array.makelen""inletn0=ref0inletn1=ref0inletn2=ref0inletn3=ref0inletstatsin=incrn0;ifn<54then(incrn1;n2:=!n2+weighti);n3:=!n3+weightiinletnm~originn=name.(origin)<-Var.to_string~origin:(Var.of_idxorigin)(Var.of_idxn)inlettotal=ref0inletbad=ref0infori=0toArray.lengtht.parameters-1doList.iter(List.revt.parameters.(i))~f:(funx->incrtotal;letidx=Var.idxxinletl=constr.(idx)inifis_availablelithen(nm~origin:idxi;mark_allocatedli;statsidxi)elseincrbad)done;ifdebug()thenFormat.eprintf"Function parameter properly assigned: %d/%d@."(!total-!bad)!total;fori=0tolen-1doletl=constr.(idx.(i))inif(not(List.is_emptyl))&&String.lengthname.(idx.(i))=0then(letn=first_availablelinletidx=idx.(i)innm~origin:idxn;mark_allocatedln;statsidxn);ifList.is_emptylthenassert(weightidx.(i)=0)done;ifdebug()then(Format.eprintf"short variable count: %d/%d@."!n1!n0;Format.eprintf"short variable occurrences: %d/%d@."!n2!n3);nameletadd_constraintsglobalu?(offset=0)params=letconstr=global.constrinletc=make_alloc_table()inS.iter(funv->leti=Var.idxvinconstr.(i)<-c::constr.(i))u;letparams=Array.of_listparamsinletlen=Array.lengthparamsinletlen_max=len+offsetinifArray.lengthglobal.parameters<len_maxthen(leta=Array.make(2*len_max)[]inArray.blit~src:global.parameters~src_pos:0~dst:a~dst_pos:0~len:(Array.lengthglobal.parameters);global.parameters<-a);fori=0tolen-1domatchparams.(i)with|Vx->global.parameters.(i+offset)<-x::global.parameters.(i+offset)|_->()done;global.constraints<-u::global.constraintsletrecord_blockstatescope~catchparams=letoffset=ifcatchthen5else0inletall=S.unionscope.Js_traverse.defscope.Js_traverse.useinadd_constraintsstateall~offsetparamsendmodulePreserve:Strategy=struct(* Try to preserve variable names.
- Assign the origin name if present: "{original_name}"
- If present but not available, derive a similar name: "{original_name}${n}" (eg. result$3).
- If not present, make up a name: "$${n}"
Color variables one scope/block at a time - outer scope first.
*)typet={size:int;mutablescopes:(S.t*Js_traverse.t)list}letcreatesize={size;scopes=[]}letrecord_blocktscope~catchparam=letdefs=matchcatch,paramwith|true,[Vx]->S.singletonx|true,[S_]->S.empty|true,_->assertfalse|false,_->scope.Js_traverse.defint.scopes<-(defs,scope)::t.scopesletallocate_variablest~count:_=letnames=Array.maket.size""inList.itert.scopes~f:(fun(defs,state)->letassigned=List.fold_left~f:StringSet.union~init:StringSet.empty[state.Js_traverse.def_name;state.Js_traverse.use_name;Reserved.keyword]inletassigned=S.fold(funvaracc->letname=names.(Var.idxvar)inifnot(String.is_emptyname)thenStringSet.addnameaccelseacc)(S.unionstate.Js_traverse.usestate.Js_traverse.def)assignedinlet_assigned=S.fold(funvarassigned->assert(String.is_emptynames.(Var.idxvar));letname=matchVar.get_namevarwith|Someexpected_name->assert(not(String.is_emptyexpected_name));ifnot(StringSet.memexpected_nameassigned)thenexpected_nameelseleti=ref0inwhileStringSet.mem(Printf.sprintf"%s$%d"expected_name!i)assigneddoincridone;Printf.sprintf"%s$%d"expected_name!i|None->Var.to_stringvarinnames.(Var.idxvar)<-name;StringSet.addnameassigned)defsassignedin());namesendclasstraverserecord_block=object(m)inheritJs_traverse.freeassupermethod!block?(catch=false)params=record_blockm#state~catchparams;super#blockparamsendletprogram'(moduleStrategy:Strategy)p=letnv=Var.count()inletstate=Strategy.createnvinletmapper=newtraverse(Strategy.record_blockstate)inletp=mapper#programpinmapper#block[];ifS.cardinalmapper#get_free<>0theniftruethenfailwith_"Some variables escaped (#%d)"(S.cardinalmapper#get_free)else(Format.eprintf"Some variables escaped (#%d)"(S.cardinalmapper#get_free);S.iter(funs->Format.eprintf"%s@."(Var.to_strings))mapper#get_free);letnames=Strategy.allocate_variablesstate~count:mapper#state.Js_traverse.countin(* if debug () then output_debug_information state coloring#state.Js_traverse.count; *)letcolor=function|Vv->letname=names.(Var.idxv)inassert(not(String.is_emptyname));ident~var:vname|x->xin(newJs_traverse.substcolor)#programpletprogramp=ifConfig.Flag.shortvar()thenprogram'(moduleMin)pelseprogram'(modulePreserve)p