123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589openWax_langopenAst(* The only ways an instruction references a local are [Get], [Set] and [Tee];
every other identifier (labels, tags, types, fields, functions...) lives in a
different namespace, so we only treat those three as uses. The remaining
cases simply recurse into sub-instructions. *)letrecoccursnamei=letin_listl=List.exists(occursname)linletin_opto=matchowithSomei->occursnamei|None->falseinmatchi.descwith|Getid->String.equalid.descname|Tee(id,e)->String.equalid.descname||occursnamee|Set(id,_,e)->String.equalid.descname||occursnamee|Block{block;_}|Loop{block;_}|TryTable{block;_}->in_listblock.desc|While{cond;step;block;_}->occursnamecond||in_optstep||in_listblock.desc|If{cond;if_block;else_block;_}->(occursnamecond||in_listif_block.desc||matchelse_blockwithSomeb->in_listb.desc|None->false)|Try{block;catches;catch_all;_}->(in_listblock.desc||List.exists(fun(_,b)->in_listb.desc)catches||matchcatch_allwithSomeb->in_listb.desc|None->false)|TryCatch{block;arms;_}->in_listblock.desc||List.exists(funa->in_lista.arm_body.desc)arms|Call(t,args)|TailCall(t,args)->occursnamet||in_listargs|Labelled(_,e)|Cast(e,_)|Test(e,_)|NonNulle|StructGet(e,_)|GetDescriptore|StructDefaultDesce|UnOp(_,e)|Br_if(_,e)|Hinted(_,e)|On(e,_)|Br_table(_,e)|Br_on_null(_,e)|Br_on_non_null(_,e)|Br_on_cast(_,_,e)|Br_on_cast_fail(_,_,e)|ThrowRefe|ArrayDefault(_,e)|ContNew(_,e)->occursnamee|Struct(_,fields)->List.exists(field_occursname)fields|StructDesc(d,fields)->occursnamed||List.exists(field_occursname)fields|CastDesc(e1,_,e2)|Br_on_cast_desc_eq(_,_,e1,e2)|Br_on_cast_desc_eq_fail(_,_,e1,e2)|StructSet(e1,_,e2)|Array(_,e1,e2)|ArraySegment(_,_,e1,e2)|ArrayGet(e1,e2)|BinOp(_,e1,e2)->occursnamee1||occursnamee2|ArraySet(e1,e2,e3)|Select(e1,e2,e3)->occursnamee1||occursnamee2||occursnamee3|ArrayFixed(_,l)|ContBind(_,_,l)|Suspend(_,l)|Resume(_,_,l)|ResumeThrow(_,_,_,l)|ResumeThrowRef(_,_,l)|Switch(_,_,l)|Throw(_,l)|Sequencel->in_listl|Dispatch{index;arms;_}->occursnameindex||List.exists(fun(_,b)->in_listb.desc)arms|Match{scrutinee;arms;default}->occursnamescrutinee||List.exists(fun(_,b)->in_listb.desc)arms||in_listdefault.desc|Let(_,body)->in_optbody|Br(_,o)|Returno->in_opto|If_annotation{then_body;else_body;_}->(in_listthen_body.desc||matchelse_bodywithSomeb->in_listb.desc|None->false)|Path_|Unreachable|Nop|Hole|Null|Char_|String_|Int_|Float_|StructDefault_->false(* A struct-literal field. A punned field [{x}] ([None]) is an implicit [Get] of
the like-named local, so it references [name] exactly when [name] is that
field. *)andfield_occursname(fn,e)=matchewithSomee->occursnamee|None->String.equalfn.descnameletlist_occursnamel=List.exists(occursname)lletbare_let(name,typ)=no_loc(Let([(Somename,Sometyp)],None))letinit_let(name,typ)einfo={desc=Let([(Somename,Sometyp)],Somee);info}(* [name = e] is fusable into [let name = e] only when the assignment is the
first use of [name] (the caller guarantees that) and [e] does not read
[name] itself: [let name = e] does not bring [name] into scope within [e],
and a bare declaration zero-initializes the local, so refusing here keeps the
read-before-write behaviour intact. *)letfusablesname=matchs.descwith|Set(id,None,e)whenString.equalid.descname&¬(occursnamee)->Somee|_->Noneletrecsplit_aroundil=matchlwith|x::r->ifi=0then([],x,r)elseletprefix,s,suffix=split_around(i-1)rin(x::prefix,s,suffix)|[]->assertfalseletfirst_use_indexnamel=letrecauxi=function|[]->None|x::r->ifoccursnamexthenSomeielseaux(i+1)rinaux0l(* The kind of the first access to [name] within [i], in execution order:
[`Read] if the local is read before any assignment to it, [`Write] if it is
assigned first, [None] if [i] does not touch it. For [Set]/[Tee] the value is
evaluated before the binding, so a self-reference there is a read seen first.
Conditional sub-scopes (the branches of an [If]/[If_annotation], catch
handlers, dispatch arms) are runtime-dependent: we commit to a kind only when
the alternatives agree, otherwise report [None] so a later instruction — or,
failing that, "not read first" — decides. *)letrecfirst_accessnamei=letfst2ab=matchawithSome_->a|None->binletfll=first_access_listnamelinletfo=functionSomee->first_accessnamee|None->Noneinletagreeab=match(a,b)withSomex,Someywhenx=y->Somex|_->Noneinmatchi.descwith|Getid->ifString.equalid.descnamethenSome`ReadelseNone|Tee(id,e)->fst2(first_accessnamee)(ifString.equalid.descnamethenSome`WriteelseNone)|Set(id,_,e)->fst2(first_accessnamee)(ifString.equalid.descnamethenSome`WriteelseNone)|Block{block;_}|Loop{block;_}|TryTable{block;_}->flblock.desc|While{cond;step;block;_}->(* One iteration reads [cond], then the body, then the step. *)fst2(first_accessnamecond)(fst2(flblock.desc)(fostep))|If{cond;if_block;else_block;_}->fst2(first_accessnamecond)(agree(flif_block.desc)(matchelse_blockwithSomeb->flb.desc|None->None))|Try{block;catches;catch_all;_}->fst2(flblock.desc)(List.fold_left(funacc(_,b)->agreeacc(flb.desc))(matchcatch_allwithSomeb->flb.desc|None->None)catches)|TryCatch{block;arms;_}->fst2(flblock.desc)(List.fold_left(funacca->agreeacc(fla.arm_body.desc))Nonearms)|Call(t,args)|TailCall(t,args)->fst2(flargs)(first_accessnamet)|Labelled(_,e)|Cast(e,_)|Test(e,_)|NonNulle|StructGet(e,_)|GetDescriptore|StructDefaultDesce|UnOp(_,e)|Br_if(_,e)|Hinted(_,e)|On(e,_)|Br_table(_,e)|Br_on_null(_,e)|Br_on_non_null(_,e)|Br_on_cast(_,_,e)|Br_on_cast_fail(_,_,e)|ThrowRefe|ArrayDefault(_,e)|ContNew(_,e)->first_accessnamee|Struct(_,fields)->field_list_accessnamefields|StructDesc(d,fields)->(* The field values are evaluated before the descriptor operand. *)fst2(field_list_accessnamefields)(first_accessnamed)|CastDesc(e1,_,e2)|Br_on_cast_desc_eq(_,_,e1,e2)|Br_on_cast_desc_eq_fail(_,_,e1,e2)|StructSet(e1,_,e2)|Array(_,e1,e2)|ArraySegment(_,_,e1,e2)|ArrayGet(e1,e2)|BinOp(_,e1,e2)->fst2(first_accessnamee1)(first_accessnamee2)|ArraySet(e1,e2,e3)|Select(e1,e2,e3)->fst2(first_accessnamee1)(fst2(first_accessnamee2)(first_accessnamee3))|ArrayFixed(_,l)|ContBind(_,_,l)|Suspend(_,l)|Resume(_,_,l)|ResumeThrow(_,_,_,l)|ResumeThrowRef(_,_,l)|Switch(_,_,l)|Throw(_,l)|Sequencel->fll|Dispatch{index;arms;_}->fst2(first_accessnameindex)(List.fold_left(funacc(_,b)->agreeacc(flb.desc))Nonearms)|Match{scrutinee;arms;default}->(* The scrutinee is evaluated first; the arms and default are the
(mutually exclusive) runtime-dependent alternatives, so commit only
when they agree. *)fst2(first_accessnamescrutinee)(List.fold_left(funacc(_,b)->agreeacc(flb.desc))(fldefault.desc)arms)|Let(_,body)->fobody|Br(_,o)|Returno->foo|If_annotation{then_body;else_body;_}->agree(flthen_body.desc)(matchelse_bodywithSomeb->flb.desc|None->None)|Path_|Unreachable|Nop|Hole|Null|Char_|String_|Int_|Float_|StructDefault_->Noneandfirst_access_listnamel=List.fold_left(funacci->matchaccwithSome_->acc|None->first_accessnamei)Nonel(* First access to [name] across a struct literal's fields, in evaluation order.
A punned field [{x}] ([None]) is an implicit [Get] of the like-named local. *)andfield_list_accessnamefields=List.fold_left(funacc(fn,e)->matchaccwith|Some_->acc|None->(matchewith|Somee->first_accessnamee|None->ifString.equalfn.descnamethenSome`ReadelseNone))Nonefields(* A loop / while body runs many times, so a local the body reads
before assigning carries its value across iterations and belongs in the
enclosing scope, not the body. A local written before it is ever read is a
fresh per-iteration temporary and is left to sink in (and fuse with its
initializer). *)letloop_carriednamel=first_access_listnamel=Some`Read(* Try to push [decl] into a sub-scope of the single instruction [s] that holds
all of its uses. Returns [None] when no inward move is possible (the caller
then places a bare declaration before [s]). A [let] is forbidden inside an
[If_annotation] branch, so those are never entered. *)letrecsink_into((name,_)asdecl)s=letblblock=sink_decldeclblockinletbl_locblock={blockwithdesc=sink_decldeclblock.desc}inletocce=occursname.descein(* Recurse into the unique sub-expression of [s] holding every use of the
local and rebuild [s] around the result. With all uses in one operand no
sibling reads the local, so their evaluation order is irrelevant; uses
split across operands, or an operand with no inner block to take the let,
yield [None] and leave a bare declaration before [s]. *)letpickchildren=matchList.filter(fun(e,_)->occe)childrenwith|[(e,mk)]->Option.map(fune'->{swithdesc=mke'})(sink_intodecle)|_->Nonein(* The [pick] children for an operand list: each element paired with the
rebuild that reinstalls it. *)letin_listlmk=List.mapi(funie->(e,fune'->mk(List.mapi(funjx->ifi=jthene'elsex)l)))linmatchs.descwith|Blockr->Some{swithdesc=Block{rwithblock=bl_locr.block}}|Loopr->(* A loop re-enters its body, so a value carried across iterations pins
the declaration outside it (see [loop_carried]). *)ifloop_carriedname.descr.block.descthenNoneelseSome{swithdesc=Loop{rwithblock=bl_locr.block}}|TryTabler->Some{swithdesc=TryTable{rwithblock=bl_locr.block}}|Whiler->(if(* The test is evaluated in the enclosing scope (re-checked each
iteration), so a use there pins the declaration outside the loop; so
does a value the body carries across iterations. *)occursname.descr.cond||loop_carriedname.descr.block.descthenNoneelsematchr.stepwith|Somestepwhenoccursname.descstep->((* The continue-expression reads the local. A labelled loop wraps its
body in a block (the continue target) and runs the step outside
that block (see [Ast_utils.lower_while]), so no declaration in the
body can reach the step — leave it before the loop. An unlabelled
loop lowers the step right after the body in the same scope, so
sink across body-and-step, then split the step back off. *)matchr.labelwith|Some_->None|None->(matchList.rev(sink_decldecl(r.block.desc@[step]))with|step'::rev_block->Some{swithdesc=While{rwithblock={r.blockwithdesc=List.revrev_block};step=Somestep';};}|[]->None))|_->Some{swithdesc=While{rwithblock=bl_locr.block}})|Ifr->(* The condition is evaluated in the enclosing scope, so a use there
pins the declaration outside the [If]; a use in both branches cannot
be covered by a single declaration. *)ifoccursname.descr.condthenNoneelseletin_then=list_occursname.descr.if_block.descinletin_else=matchr.else_blockwith|Someb->list_occursname.descb.desc|None->falseinifin_then&¬in_elsethenSome{swithdesc=If{rwithif_block={r.if_blockwithdesc=blr.if_block.desc};};}elseifin_else&¬in_thenthenSome{swithdesc=If{rwithelse_block=Option.map(funb->{bwithdesc=blb.desc})r.else_block;};}elseNone|Tryr->letin_block=list_occursname.descr.block.descinletn_catches=List.length(List.filter(fun(_,b)->list_occursname.descb.desc)r.catches)inletin_all=matchr.catch_allwith|Someb->list_occursname.descb.desc|None->falseinletcount=(ifin_blockthen1else0)+n_catches+ifin_allthen1else0inifcount<>1thenNoneelseifin_blockthenSome{swithdesc=Try{rwithblock=bl_locr.block}}elseifin_allthenSome{swithdesc=Try{rwithcatch_all=Option.mapbl_locr.catch_all};}elseletcatches=List.map(fun(tag,b)->iflist_occursname.descb.descthen(tag,bl_locb)else(tag,b))r.catchesinSome{swithdesc=Try{rwithcatches}}|Dispatchr->(* The index is evaluated in the enclosing scope, so a use there pins the
declaration outside; the arms are mutually exclusive, so a single
declaration can cover at most one. *)ifoccr.indexthenNoneelseifList.length(List.filter(fun(_,b)->list_occursname.descb.desc)r.arms)<>1thenNoneelseletarms=List.map(fun(l,b)->iflist_occursname.descb.descthen(l,bl_locb)else(l,b))r.armsinSome{swithdesc=Dispatch{rwitharms}}(* Expression carriers: descend through an operand toward a nested block. *)|Set(id,op,e)whennot(String.equalid.descname.desc)->(* An assigned block expression — [x = do {..}] — holds all uses in its
body; sink into it. (A discarded block, [_ = do {..}], is an anonymous
[Let] and is handled by the [Let] case below.) We exclude [name = e]:
there [e] is this local's own initializer (the non-reading case is
already fused by [sink_decl]), and sinking the declaration into [e]
would shadow it. *)pick[(e,fune'->Set(id,op,e'))]|Tee(n,e)whennot(String.equaln.descname.desc)->pick[(e,fune'->Tee(n,e'))]|Let(bindings,Somee)whennot(List.exists(fun(id,_)->matchidwith|Somen->String.equaln.descname.desc|None->false)bindings)->(* [let y = <block>] (binding some other local) holds every use of [name]
in its initializer; descend into it, as for [Set]. The initializer is
evaluated before [y] enters scope, so placing the declaration inside it
does not shadow [name]. *)pick[(e,fune'->Let(bindings,Somee'))]|Call(t,args)->pick((t,funt'->Call(t',args))::in_listargs(funargs'->Call(t,args')))|TailCall(t,args)->pick((t,funt'->TailCall(t',args))::in_listargs(funargs'->TailCall(t,args')))|BinOp(op,a,b)->pick[(a,funa'->BinOp(op,a',b));(b,funb'->BinOp(op,a,b'))]|ArrayGet(a,b)->pick[(a,funa'->ArrayGet(a',b));(b,funb'->ArrayGet(a,b'))]|Array(idx,a,b)->pick[(a,funa'->Array(idx,a',b));(b,funb'->Array(idx,a,b'))]|ArraySegment(idx,d,a,b)->pick[(a,funa'->ArraySegment(idx,d,a',b));(b,funb'->ArraySegment(idx,d,a,b'));]|StructSet(a,f,b)->pick[(a,funa'->StructSet(a',f,b));(b,funb'->StructSet(a,f,b'));]|ArraySet(a,b,c)->pick[(a,funa'->ArraySet(a',b,c));(b,funb'->ArraySet(a,b',c));(c,func'->ArraySet(a,b,c'));]|Select(a,b,c)->pick[(a,funa'->Select(a',b,c));(b,funb'->Select(a,b',c));(c,func'->Select(a,b,c'));]|Struct(idx,fields)->(* A punned field ([None]) has no sub-expression to sink into, so only
explicit field values become sink candidates. *)pick(List.filter_map(fun(i,(_,e))->Option.map(fune->(e,fune'->Struct(idx,List.mapi(funj(fn,x)->(fn,ifi=jthenSomee'elsex))fields)))e)(List.mapi(funif->(i,f))fields))|Cast(e,t)->pick[(e,fune'->Cast(e',t))]|Test(e,t)->pick[(e,fune'->Test(e',t))]|NonNulle->pick[(e,fune'->NonNulle')]|StructGet(e,f)->pick[(e,fune'->StructGet(e',f))]|UnOp(op,e)->pick[(e,fune'->UnOp(op,e'))]|ThrowRefe->pick[(e,fune'->ThrowRefe')]|ArrayDefault(idx,e)->pick[(e,fune'->ArrayDefault(idx,e'))]|ContNew(ct,e)->pick[(e,fune'->ContNew(ct,e'))]|Br_if(l,e)->pick[(e,fune'->Br_if(l,e'))]|Hinted(h,e)->pick[(e,fune'->Hinted(h,e'))]|On(e,h)->pick[(e,fune'->On(e',h))]|Br_table(ls,e)->pick[(e,fune'->Br_table(ls,e'))]|Br_on_null(l,e)->pick[(e,fune'->Br_on_null(l,e'))]|Br_on_non_null(l,e)->pick[(e,fune'->Br_on_non_null(l,e'))]|Br_on_cast(l,t,e)->pick[(e,fune'->Br_on_cast(l,t,e'))]|Br_on_cast_fail(l,t,e)->pick[(e,fune'->Br_on_cast_fail(l,t,e'))]|Br(l,Somee)->pick[(e,fune'->Br(l,Somee'))]|Return(Somee)->pick[(e,fune'->Return(Somee'))]|Throw(idx,l)->pick(in_listl(funl'->Throw(idx,l')))|ArrayFixed(idx,l)->pick(in_listl(funl'->ArrayFixed(idx,l')))|ContBind(a,b,l)->pick(in_listl(funl'->ContBind(a,b,l')))|Suspend(tag,l)->pick(in_listl(funl'->Suspend(tag,l')))|Resume(ct,h,l)->pick(in_listl(funl'->Resume(ct,h,l')))|ResumeThrow(ct,tag,h,l)->pick(in_listl(funl'->ResumeThrow(ct,tag,h,l')))|ResumeThrowRef(ct,h,l)->pick(in_listl(funl'->ResumeThrowRef(ct,h,l')))|Switch(ct,tag,l)->pick(in_listl(funl'->Switch(ct,tag,l')))|Sequencel->pick(in_listl(funl'->Sequencel'))|_->None(* Place a single declaration into [l]. Precondition: every use of the local
lies within [l], and nothing before [l] uses it. *)andsink_decl((name,_)asdecl)l=matchfirst_use_indexname.desclwith|None->bare_letdecl::l|Somei->(letprefix,s,suffix=split_aroundilinmatchfusablesname.descwith|Somee->prefix@(init_letdecles.info::suffix)|None->(iflist_occursname.descsuffixthenprefix@(bare_letdecl::s::suffix)elsematchsink_intodeclswith|Somes'->prefix@(s'::suffix)|None->prefix@(bare_letdecl::s::suffix)))letrecextract_leading_declsaccinstrs=matchinstrswith|{desc=Let([(Somename,Sometyp)],None);_}::rest->extract_leading_decls((name,typ)::acc)rest|_->(List.revacc,instrs)letprocess_bodyinstrs=letdecls,rest=extract_leading_decls[]instrsin(* An unused declaration has no sink target, so [sink_decl] just prepends it.
Folding such declarations would reverse their order on each pass (and
[Sink_let] runs on every conversion to Wax), so the round-trip would never
settle. Place the used ones via the fold, then prepend the unused ones in
their original order so the output is a fixpoint. *)letused,unused=List.partition(fun(name,_)->list_occursname.descrest)declsinletbody=List.fold_left(funaccdecl->sink_decldeclacc)restusedinList.fold_right(fundeclacc->bare_letdecl::acc)unusedbodyletrecfield_desc(f:locationmodulefield)=letmap_fields=List.map(funa->{awithdesc=field_desca.desc})inmatchfwith|Func({body=label,instrs;_}asr)->Func{rwithbody=(label,process_bodyinstrs)}|Conditional({then_fields;else_fields;_}asr)->Conditional{rwiththen_fields={then_fieldswithdesc=map_fieldsthen_fields.desc};else_fields=Option.map(funb->{bwithdesc=map_fieldsb.desc})else_fields;}|(Type_|Module_annotation_|Import_|Import_group_|Global_|Tag_|Memory_|Data_|Table_|Elem_)asf->fletmodule_(m:locationmodule_):locationmodule_=List.map(funa->{awithdesc=field_desca.desc})m