123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445openWax_langopenAst(* Recover a structured [try] from the [try_table]-plus-block-ladder shape that
[Ast_utils.lower_trycatch] emits (and that compilers targeting [try_table]
conventionally use): a [join] block wrapping one block per arm (the first
arm innermost), the [try_table] innermost — its value escaping with a
[br 'join] — one catch clause per arm branching to that arm's block, and
each arm body as the trailing code just after its block:
'join: { 'a2: { 'a1: { br 'join (try { body } catch [t1 -> 'a1, t2 -> 'a2]) } b1 } b2 }
⇒ 'join: try { body } catch { t1 => { b1 } t2 => { b2 } }
Arms need not end in a branch: a fall-through arm (its completion feeding
the next arm's entry, or the join) and a trailing-diverging arm recover
directly. Folding is the exact inverse of the lowering — re-lowering
reproduces the original blocks — so it preserves runtime semantics; the
matcher confirms the shape:
- the catch clauses target the ladder labels in order, innermost first, one
clause per ladder block, and nothing else targets them (the arm labels
have no surface spelling);
- a catch-all clause only appears last (the grammar puts the [_] arm last);
- the [try_table]'s own label, if any, is untargeted (a branch to it has no
structured equivalent);
- every block is parameterless.
The recovered try keeps the join label only when the bodies still target it
(the explicit-escape idiom [br 'join v]); otherwise it is dropped, so a
label-less source try round-trips identically. Ladders that do not conform
are left as-is: the bracket form ([try { … } catch [t -> 'l]]) remains the
fallback spelling. *)letno_params(t:functype)=t.params=[||](* Labels an instruction *targets* (not defines): the shape check requires the
ladder labels to be reachable only through the [try_table]'s catch clauses.
Shadowing by an inner definition is ignored — a shadowed use only makes the
check conservatively bail. *)lettarget_labels(desc:locationinstr_desc):labellist=matchdescwith|Br(l,_)|Br_if(l,_)|Br_on_null(l,_)|Br_on_non_null(l,_)|Br_on_cast(l,_,_)|Br_on_cast_fail(l,_,_)|Br_on_cast_desc_eq(l,_,_,_)|Br_on_cast_desc_eq_fail(l,_,_,_)->[l]|Br_table(ls,_)->ls|Dispatch{cases;default;_}->cases@[default]|TryTable{catches;_}->List.map(function|Catch(_,l)|CatchRef(_,l)|CatchAlll|CatchAllRefl->l)catches|Resume(_,hs,_)|ResumeThrow(_,_,hs,_)|ResumeThrowRef(_,hs,_)|On(_,hs)->List.filter_map(functionOnLabel(_,l)->Somel|OnSwitch_->None)hs|_->[]lettargetsnameinstrs=letfound=reffalseinList.iter(Ast_utils.iter_instr(funi->ifList.exists(fun(l:label)->l.desc=name)(target_labelsi.desc)thenfound:=true))instrs;!found(* When the first statement of a peel level embeds the next ladder block as
its first-evaluated operand — [From_wasm] merges a produced value into its
consumer, so [wrap(block)], [_ = block], [br 'j block] — extract it: return
the block's pieces and the statement with the block replaced by a hole,
which is how the source arm spells the payload pickup. Extraction only
descends along positions [To_wasm] evaluates first (so the hole stays the
first value the statement pulls from the stack): a call's first argument
only for a plain named-function callee — a method receiver or indirect call
evaluates differently, and any unrecognized consumer just leaves the ladder
to the bracket-form fallback. *)letrecextract_first_block(i:locationinstr)=letsuberebuild=Option.map(fun(l,t,inner,e')->(l,t,inner,{iwithdesc=rebuilde'}))(extract_first_blocke)inmatchi.descwith|Block{label=Somel;typ;block={desc=inner;_}}->Some(l,typ,inner,{iwithdesc=Hole})|Let(pats,Somee)->sube(fune'->Let(pats,Somee'))|Br(lb,Somee)->sube(fune'->Br(lb,Somee'))|Br_if(lb,e)->sube(fune'->Br_if(lb,e'))|Br_table(ls,e)->sube(fune'->Br_table(ls,e'))|Return(Somee)->sube(fune'->Return(Somee'))|Set(x,op,e)->sube(fune'->Set(x,op,e'))|Tee(x,e)->sube(fune'->Tee(x,e'))|Cast(e,ty)->sube(fune'->Cast(e',ty))|Test(e,ty)->sube(fune'->Test(e',ty))|NonNulle->sube(fune'->NonNulle')|StructGet(e,f)->sube(fune'->StructGet(e',f))|GetDescriptore->sube(fune'->GetDescriptore')|UnOp(op,e)->sube(fune'->UnOp(op,e'))|BinOp(op,a,b)->suba(funa'->BinOp(op,a',b))|ThrowRefe->sube(fune'->ThrowRefe')|Throw(t,a::args)->suba(funa'->Throw(t,a'::args))|Call(({desc=Get_;_}asf),a::args)->suba(funa'->Call(f,a'::args))|TailCall(({desc=Get_;_}asf),a::args)->suba(funa'->TailCall(f,a'::args))|_->None(* Peel the arm-block ladder inside the join block: collect each ladder
block's (label, result types, trailing body), outermost first, down to the
innermost content — the [try_table] escaping to [join]. A ladder block is
either the leading statement of its level or embedded in that statement as
its first-evaluated operand (see {!extract_first_block}). *)letrecdescendjoinblock=matchblockwith|[{desc=Br((j:label),Some{desc=TryTable{label=None;typ;catches;block};_});_;};]whenj.desc=join&&no_paramstyp&&typ.results<>[||]->Some([],typ,catches,block)|[{desc=TryTable{label=None;typ;catches;block};_};{desc=Br((j:label),None);_};]whenj.desc=join&&no_paramstyp&&typ.results=[||]->Some([],typ,catches,block)|{desc=Block{label=Somel;typ;block={desc=inner;_}};_}::bodywhenno_paramstyp->(matchdescendjoininnerwith|Some(arms,ttyp,catches,tbody)->Some((l,typ.results,body)::arms,ttyp,catches,tbody)|None->None)|first::body->(matchextract_first_blockfirstwith|Some(l,typ,inner,first')whenno_paramstyp->(matchdescendjoininnerwith|Some(arms,ttyp,catches,tbody)->Some((l,typ.results,first'::body)::arms,ttyp,catches,tbody)|None->None)|_->None)|[]->None(* Recurse into children structurally. *)letrecrewrite_instr(i:locationinstr):locationinstr=matchtry_foldiwith|Somefolded->folded|None->{iwithdesc=rewrite_desci.desc}andrewrite_listl=List.maprewrite_instrlandtry_fold(i:locationinstr):locationinstroption=matchi.descwith|Block{label=Somejoin;typ;block}whenno_paramstyp->(matchdescendjoin.descblock.descwith|Some(rev_arms,ttyp,catches,tbody)whencatches<>[]->((* Ladder blocks come out outermost first; catch clauses are in arm
order (innermost first). *)letblocks=List.revrev_armsinifList.lengthblocks<>List.lengthcatchesthenNoneelseifnot(Array.lengthttyp.results=Array.lengthtyp.results)(* The try_table's result type is the join's (the escaping [br]
carries it); a mismatch is not our shape. *)thenNoneelseletarm(catch:catch)((l:label),types,body)=lettag,ref_,target=matchcatchwith|Catch(t,tl)->(Somet,false,tl)|CatchRef(t,tl)->(Somet,true,tl)|CatchAlltl->(None,false,tl)|CatchAllReftl->(None,true,tl)iniftarget.desc<>l.descthenNoneelseSome{arm_tag=tag;arm_ref=ref_;arm_types=types;arm_body=no_locbody;}inletrecbuildcatchesblocks=match(catches,blocks)with|[],[]->Some[]|c::cs,b::bs->(match(armcb,buildcsbs)with|Somea,Somerest->Some(a::rest)|_->None)|_->Noneinmatchbuildcatchesblockswith|Somearmswhen(* the catch-all is grammar-enforced last *)List.for_all(funa->a.arm_tag<>None)(matchList.revarmswith|[]->[]|_::init_rev->init_rev)(* nothing but its catch clause may target a ladder label *)&&(letall_bodies=tbody.desc@List.concat_map(funa->a.arm_body.desc)armsinList.for_all(fun((l:label),_,_)->not(targetsl.descall_bodies))blocks)(* arm labels are distinct (a shared label would alias) *)&&letnames=List.map(fun((l:label),_,_)->l.desc)blocksinList.length(List.sort_uniqcomparenames)=List.lengthnames->letall_bodies=tbody.desc@List.concat_map(funa->a.arm_body.desc)armsinletlabel=iftargetsjoin.descall_bodiesthenSomejoinelseNoneinSome{iwithdesc=TryCatch{label;typ;block={tbodywithdesc=rewrite_listtbody.desc};arms=List.map(funa->{awitharm_body={a.arm_bodywithdesc=rewrite_lista.arm_body.desc;};})arms;};}|_->None)|_->None)|_->Noneandrewrite_desc(desc:locationinstr_desc):locationinstr_desc=matchdescwith|Block{label;typ;block}->Block{label;typ;block={blockwithdesc=rewrite_listblock.desc}}|Loop{label;typ;block}->Loop{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