123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391openWax_langopenAst(* Recover the high-level [while] loop from the [loop] shape that
[Ast_utils.lower_while] produces (and that compilers emit for the
corresponding source):
'L: loop { if C { B; br 'L; } } ⇒ 'L?: while C { B } (leading test)
so decompiled WAT/WASM (and round-tripped Wax loops) read as the high-level
form rather than a bare [loop] with an explicit back-edge. A trailing-test
[loop { B; br_if 'L C; }] has no leading-test [while] equivalent, so it is
left as a bare [loop].
The synthesised loop label is kept on the recovered loop only when the body
still branches to it after the back-edge is removed (a "continue"); otherwise
it is dropped and the label-less form re-lowers to a fresh synthetic label.
The reference test is exhaustive and conservative — erring toward keeping the
label is safe, dropping a still-referenced one would dangle — so a missed
constructor is a compile error, not silently wrong output.
Folding is the exact inverse of the lowering, so re-lowering reproduces the
original [loop] byte-for-byte and the rewrite always preserves runtime
semantics. Meant to run on {!From_wasm.module_} output, after
{!Recover_dispatch.module_} and before {!Sink_let.module_} (which would
otherwise sink locals into the loop body and hide the shape). *)letis_void(t:functype)=t.params=[||]&&t.results=[||](* Whether any branch within [i] targets the label [name] — a "continue" to a
recovered loop, which forces the label to be kept. *)letrecrefs_instrname(i:locationinstr):bool=letany=List.exists(refs_instrname)inletopt=functionSomex->refs_instrnamex|None->falseinletlab(l:label)=String.equall.descnameinmatchi.descwith|Br(l,e)->labl||opte|Br_if(l,e)->labl||refs_instrnamee|Br_table(ls,e)->List.existslabls||refs_instrnamee|Hinted(_,e)|On(e,_)->refs_instrnamee|Br_on_null(l,e)|Br_on_non_null(l,e)->labl||refs_instrnamee|Br_on_cast(l,_,e)|Br_on_cast_fail(l,_,e)->labl||refs_instrnamee|Br_on_cast_desc_eq(l,_,e,d)|Br_on_cast_desc_eq_fail(l,_,e,d)->labl||refs_instrnamee||refs_instrnamed|Dispatch{index;cases;default;arms}->labdefault||List.existslabcases||refs_instrnameindex||List.exists(fun(_,b)->anyb.desc)arms|Match{scrutinee;arms;default}->refs_instrnamescrutinee||List.exists(fun(_,b)->anyb.desc)arms||anydefault.desc|Block{block;_}|Loop{block;_}|TryTable{block;_}->anyblock.desc|While{cond;step;block;_}->refs_instrnamecond||optstep||anyblock.desc|If{cond;if_block;else_block;_}->(refs_instrnamecond||anyif_block.desc||matchelse_blockwithSomeb->anyb.desc|None->false)|Try{block;catches;catch_all;_}->(anyblock.desc||List.exists(fun(_,b)->anyb.desc)catches||matchcatch_allwithSomeb->anyb.desc|None->false)|TryCatch{block;arms;_}->anyblock.desc||List.exists(funa->anya.arm_body.desc)arms|If_annotation{then_body;else_body;_}->(anythen_body.desc||matchelse_bodywithSomeb->anyb.desc|None->false)|Set(_,_,e)|Tee(_,e)|Labelled(_,e)|Cast(e,_)|Test(e,_)|NonNulle|StructGet(e,_)|GetDescriptore|StructDefaultDesce|ArrayDefault(_,e)|UnOp(_,e)|ThrowRefe|ContNew(_,e)->refs_instrnamee|Call(a,l)|TailCall(a,l)->refs_instrnamea||anyl|Struct(_,fs)->List.exists(fun(_,e)->opte)fs|StructDesc(d,fs)->refs_instrnamed||List.exists(fun(_,e)->opte)fs|CastDesc(a,_,b)|StructSet(a,_,b)|Array(_,a,b)|ArraySegment(_,_,a,b)|ArrayGet(a,b)|BinOp(_,a,b)->refs_instrnamea||refs_instrnameb|ArraySet(a,b,c)|Select(a,b,c)->refs_instrnamea||refs_instrnameb||refs_instrnamec|ArrayFixed(_,l)|ContBind(_,_,l)|Suspend(_,l)|Resume(_,_,l)|ResumeThrow(_,_,_,l)|ResumeThrowRef(_,_,l)|Switch(_,_,l)|Throw(_,l)|Sequencel->anyl|Let(_,e)|Returne->opte|Unreachable|Nop|Hole|Null|Get_|Path_|Char_|String_|Int_|Float_|StructDefault_->falseletrefs_listnamel=List.exists(refs_instrname)l(* Whether the (already-rewritten) body of void loop [l] still references [l]
after dropping its back-edge, with [cond] the loop's recovered test. *)letkeep_labellcondbody=ifrefs_listl.descbody||refs_instrl.desccondthenSomelelseNone(* Whether [i] reads the variable [name] (a [Get]). Conservative: unlisted forms
report [false], which only makes the induction-step heuristic in [fold_loop]
fire less often, never wrongly. *)letrecreads_varname(i:locationinstr):bool=letany=List.exists(reads_varname)inmatchi.descwith|Getid->String.equalid.descname|BinOp(_,a,b)|ArrayGet(a,b)->reads_varnamea||reads_varnameb|UnOp(_,e)|Cast(e,_)|Test(e,_)|NonNulle|StructGet(e,_)|GetDescriptore|Hinted(_,e)|On(e,_)->reads_varnamee|Select(a,b,c)->reads_varnamea||reads_varnameb||reads_varnamec|Call(f,args)|TailCall(f,args)->reads_varnamef||anyargs|Tee(id,e)->String.equalid.descname||reads_varnamee|Set(id,_,e)->String.equalid.descname||reads_varnamee|Block{block;_}|Loop{block;_}->anyblock.desc|If{cond;if_block;else_block;_}->(reads_varnamecond||anyif_block.desc||matchelse_blockwithSomeb->anyb.desc|None->false)|_->false(* Fold an already-rewritten void [loop] labelled [l] into a [while] when its
body is the leading-test shape, else leave it a [loop]. *)letfold_loopltypblock=matchblock.descwith(* Leading test: the body is a single label-less void [if] with no else whose
own body ends in the back-edge. *)|[{desc=If{label=None;typ=it;cond;if_block;else_block=None};_;};]whenis_voidit->(matchList.revif_block.descwith|{desc=Br(bl,None);_}::rev_bodywhenString.equalbl.descl.desc->(letbody=List.revrev_bodyinmatchbodywith(* Continue-expression shape (see [Ast_utils.lower_while]): the body is
a labelled block (the continue target) whose own body branches to it,
followed by the step, then the loop back-edge. Recover it as
[while 'blk cond : (step) { inner }]. *)|[{desc=Block{label=Someblk;typ=bt;block=inner};_};step;]whenis_voidbt&&(not(String.equalblk.descl.desc))&&refs_listblk.descinner.desc->While{label=Someblk;cond;step=Somestep;block=inner}|_->(letlabel=keep_labellcondbodyin(* Readability recovery: with no continue (label dropped, so
re-lowering is byte-identical), present a trailing
induction-variable update — an assignment to a variable the
condition reads — as a continue-expression, so index-and-stride
loops read as [while i <s n : (i += 1) { … }]. Kept conservative:
at least one other body statement must remain. *)match(label,List.revbody)with|(None,({desc=Set(x,_,_);_}asstep)::(_::_asrev_rest))whenreads_varx.desccond->While{label=None;cond;step=Somestep;block={blockwithdesc=List.revrev_rest};}|_->While{label;cond;step=None;block={blockwithdesc=body};}))|_->Loop{label=Somel;typ;block})|_->Loop{label=Somel;typ;block}letrecrewrite_instr(i:locationinstr):locationinstr={iwithdesc=rewrite_desci.desc}andrewrite_listl=List.maprewrite_instrlandrewrite_desc(desc:locationinstr_desc):locationinstr_desc=matchdescwith|Loop{label=Somel;typ;block}whenis_voidtyp->fold_loopltyp{blockwithdesc=rewrite_listblock.desc}|Loop{label;typ;block}->Loop{label;typ;block={blockwithdesc=rewrite_listblock.desc}}|Block{label;typ;block}->Block{label;typ;block={blockwithdesc=rewrite_listblock.desc}}|While{label;cond;step;block}->While{label;cond=rewrite_instrcond;step=Option.maprewrite_instrstep;block={blockwithdesc=rewrite_listblock.desc};}|If{label;typ;cond;if_block;else_block}->If{label;typ;cond=rewrite_instrcond;if_block={if_blockwithdesc=rewrite_listif_block.desc};else_block=Option.map(funb->{bwithdesc=rewrite_listb.desc})else_block;}|TryTable{label;typ;catches;block}->TryTable{label;typ;catches;block={blockwithdesc=rewrite_listblock.desc};}|TryCatch{label;typ;block;arms}->TryCatch{label;typ;block={blockwithdesc=rewrite_listblock.desc};arms=List.map(funa->{awitharm_body={a.arm_bodywithdesc=rewrite_lista.arm_body.desc};})arms;}|Try{label;typ;block;catches;catch_all}->Try{label;typ;block={blockwithdesc=rewrite_listblock.desc};catches=List.map(fun(t,l)->(t,{lwithdesc=rewrite_listl.desc}))catches;catch_all=Option.map(funb->{bwithdesc=rewrite_listb.desc})catch_all;}|Dispatch{index;cases;default;arms}->Dispatch{index=rewrite_instrindex;cases;default;arms=List.map(fun(l,b)->(l,{bwithdesc=rewrite_listb.desc}))arms;}|Match{scrutinee;arms;default}->Match{scrutinee=rewrite_instrscrutinee;arms=List.map(fun(p,b)->(p,{bwithdesc=rewrite_listb.desc}))arms;default={defaultwithdesc=rewrite_listdefault.desc};}|If_annotation{cond;then_body;else_body}->If_annotation{cond;then_body={then_bodywithdesc=rewrite_listthen_body.desc};else_body=Option.map(funb->{bwithdesc=rewrite_listb.desc})else_body;}|Set(x,op,e)->Set(x,op,rewrite_instre)|Tee(x,e)->Tee(x,rewrite_instre)|Labelled(l,e)->Labelled(l,rewrite_instre)|Call(t,args)->Call(rewrite_instrt,List.maprewrite_instrargs)|TailCall(t,args)->TailCall(rewrite_instrt,List.maprewrite_instrargs)|Cast(e,t)->Cast(rewrite_instre,t)|CastDesc(e,t,d)->CastDesc(rewrite_instre,t,rewrite_instrd)|Test(e,t)->Test(rewrite_instre,t)|NonNulle->NonNull(rewrite_instre)|Struct(idx,fs)->Struct(idx,List.map(fun(n,e)->(n,Option.maprewrite_instre))fs)|StructDesc(d,fs)->StructDesc(rewrite_instrd,List.map(fun(n,e)->(n,Option.maprewrite_instre))fs)|StructDefaultDescd->StructDefaultDesc(rewrite_instrd)|StructGet(e,x)->StructGet(rewrite_instre,x)|GetDescriptore->GetDescriptor(rewrite_instre)|StructSet(e,x,v)->StructSet(rewrite_instre,x,rewrite_instrv)|Array(idx,a,b)->Array(idx,rewrite_instra,rewrite_instrb)|ArrayDefault(idx,e)->ArrayDefault(idx,rewrite_instre)|ArrayFixed(idx,l)->ArrayFixed(idx,List.maprewrite_instrl)|ArraySegment(idx,d,a,b)->ArraySegment(idx,d,rewrite_instra,rewrite_instrb)|ArrayGet(a,b)->ArrayGet(rewrite_instra,rewrite_instrb)|ArraySet(a,b,c)->ArraySet(rewrite_instra,rewrite_instrb,rewrite_instrc)|BinOp(op,a,b)->BinOp(op,rewrite_instra,rewrite_instrb)|UnOp(op,e)->UnOp(op,rewrite_instre)|Let(bs,e)->Let(bs,Option.maprewrite_instre)|Br(l,e)->Br(l,Option.maprewrite_instre)|Br_if(l,e)->Br_if(l,rewrite_instre)|Hinted(h,e)->Hinted(h,rewrite_instre)|On(e,h)->On(rewrite_instre,h)|Br_table(ls,e)->Br_table(ls,rewrite_instre)|Br_on_null(l,e)->Br_on_null(l,rewrite_instre)|Br_on_non_null(l,e)->Br_on_non_null(l,rewrite_instre)|Br_on_cast(l,t,e)->Br_on_cast(l,t,rewrite_instre)|Br_on_cast_fail(l,t,e)->Br_on_cast_fail(l,t,rewrite_instre)|Br_on_cast_desc_eq(l,t,e,d)->Br_on_cast_desc_eq(l,t,rewrite_instre,rewrite_instrd)|Br_on_cast_desc_eq_fail(l,t,e,d)->Br_on_cast_desc_eq_fail(l,t,rewrite_instre,rewrite_instrd)|Throw(idx,e)->Throw(idx,List.maprewrite_instre)|ThrowRefe->ThrowRef(rewrite_instre)|ContNew(ct,e)->ContNew(ct,rewrite_instre)|ContBind(src,dst,l)->ContBind(src,dst,List.maprewrite_instrl)|Suspend(tag,l)->Suspend(tag,List.maprewrite_instrl)|Resume(ct,h,l)->Resume(ct,h,List.maprewrite_instrl)|ResumeThrow(ct,tag,h,l)->ResumeThrow(ct,tag,h,List.maprewrite_instrl)|ResumeThrowRef(ct,h,l)->ResumeThrowRef(ct,h,List.maprewrite_instrl)|Switch(ct,tag,l)->Switch(ct,tag,List.maprewrite_instrl)|Returne->Return(Option.maprewrite_instre)|Sequencel->Sequence(List.maprewrite_instrl)|Select(a,b,c)->Select(rewrite_instra,rewrite_instrb,rewrite_instrc)|(Unreachable|Nop|Hole|Null|Get_|Path_|Char_|String_|Int_|Float_|StructDefault_)asx->xletrecfield_desc(f:locationmodulefield)=letmap_fields=List.map(funa->{awithdesc=field_desca.desc})inmatchfwith|Func({body=label,instrs;_}asr)->Func{rwithbody=(label,rewrite_listinstrs)}|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