123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281openWax_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=matchi.descwith|Br(l,e)->String.equall.descname||refs_optnamee|Br_if(l,e)->String.equall.descname||refs_instrnamee|Br_table(ls,e)->List.exists(funl->String.equall.Annot.descname)ls||refs_instrnamee|On(e,_)->refs_instrnamee|Br_on_null(l,e)|Br_on_non_null(l,e)->String.equall.descname||refs_instrnamee|Br_on_cast(l,_,e)|Br_on_cast_fail(l,_,e)->String.equall.descname||refs_instrnamee|Br_on_cast_desc_eq(l,_,e,d)|Br_on_cast_desc_eq_fail(l,_,e,d)->String.equall.descname||refs_instrnamee||refs_instrnamed|Dispatch{index;cases;default;arms}->String.equaldefault.descname||List.exists(funl->String.equall.Annot.descname)cases||refs_instrnameindex||List.exists(fun(_,(b:(_instrlist,_)Ast.annotated))->refs_instrsnameb.desc)arms|Match{scrutinee;arms;default}->refs_instrnamescrutinee||List.exists(fun(_,(b:(_instrlist,_)Ast.annotated))->refs_instrsnameb.desc)arms||refs_instrsnamedefault.desc|Block{block;_}|Loop{block;_}|TryTable{block;_}->refs_instrsnameblock.desc|While{cond;step;block;_}->refs_instrnamecond||refs_optnamestep||refs_instrsnameblock.desc|If{cond;if_block;else_block;_}->(refs_instrnamecond||refs_instrsnameif_block.desc||matchelse_blockwith|Someb->refs_instrsnameb.desc|None->false)|Try{block;catches;catch_all;_}->(refs_instrsnameblock.desc||List.exists(fun(_,b)->refs_instrsnameb.Annot.desc)catches||matchcatch_allwith|Someb->refs_instrsnameb.desc|None->false)|TryCatch{block;arms;_}->refs_instrsnameblock.desc||List.exists(funa->refs_instrsnamea.arm_body.desc)arms|If_annotation{then_body;else_body;_}->(refs_instrsnamethen_body.desc||matchelse_bodywith|Someb->refs_instrsnameb.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||refs_instrsnamel|Struct(_,fs)->List.exists(fun(_,e)->refs_optnamee)fs|StructDesc(d,fs)->refs_instrnamed||List.exists(fun(_,e)->refs_optnamee)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->refs_instrsnamel|Let(_,e)|Returne->refs_optnamee|Unreachable|Nop|Hole|Null|Get_|Path_|Char_|String_|Int_|Float_|StructDefault_->falseandrefs_instrsnamel=matchlwith[]->false|i::r->refs_instrnamei||refs_instrsnamerandrefs_optname=functionSomex->refs_instrnamex|None->falseletrefs_list=refs_instrs(* 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.Annot.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=matchi.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|On(e,_)->reads_varnamee|Select(a,b,c)->reads_varnamea||reads_varnameb||reads_varnamec|Call(f,args)|TailCall(f,args)->reads_varnamef||reads_var_listnameargs|Tee(id,e)->String.equalid.descname||reads_varnamee|Set(id,_,e)->String.equalid.descname||reads_varnamee|Block{block;_}|Loop{block;_}->reads_var_listnameblock.desc|If{cond;if_block;else_block;_}->(reads_varnamecond||reads_var_listnameif_block.desc||matchelse_blockwith|Someb->reads_var_listnameb.desc|None->false)|_->falseandreads_var_listnamel=matchlwith|[]->false|i::r->reads_varnamei||reads_var_listnamer(* 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_loopltyp(block:(_Ast.instrlist,_)Ast.annotated)=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.Annot.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=letd=rewrite_desci.descinifd==i.descthenielse{iwithdesc=d}andrewrite_listl=Ast_utils.smart_maprewrite_instrlandrewrite_desc(desc:locationinstr_desc):locationinstr_desc=matchdescwith|Loop{label=Somel;typ;block}whenis_voidtyp->fold_loopltyp{blockwithdesc=rewrite_listblock.desc}|d->(* structural, share-preserving; the loop recovery is the case above *)Ast_utils.map_desc~instr:rewrite_instr~block:rewrite_listdletrecfield_desc(f:locationmodulefield)=letmap_fields=List.map(fun(a:(locationmodulefield,location)Ast.annotated)->{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(fun(b:((locationmodulefield,location)Ast.annotatedlist,location)Ast.annotated)->{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(fun(a:(locationmodulefield,location)Ast.annotated)->{awithdesc=field_desca.desc})m