123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636openAstletrecmap_instrfinstr=letdesc=matchinstr.descwith|Block{label;typ;block}->Block{label;typ;block={blockwithdesc=List.map(map_instrf)block.desc};}|Loop{label;typ;block}->Loop{label;typ;block={blockwithdesc=List.map(map_instrf)block.desc};}|While{label;cond;step;block}->While{label;cond=map_instrfcond;step=Option.map(map_instrf)step;block={blockwithdesc=List.map(map_instrf)block.desc};}|If{label;typ;cond;if_block;else_block}->If{label;typ;cond=map_instrfcond;if_block={if_blockwithdesc=List.map(map_instrf)if_block.desc};else_block=Option.map(funb->{bwithdesc=List.map(map_instrf)b.desc})else_block;}|TryTable{label;typ;block;catches}->TryTable{label;typ;block={blockwithdesc=List.map(map_instrf)block.desc};catches;}|Try{label;typ;block;catches;catch_all}->Try{label;typ;block={blockwithdesc=List.map(map_instrf)block.desc};catches=List.map(fun(tag,block)->(tag,{blockwithdesc=List.map(map_instrf)block.desc}))catches;catch_all=Option.map(funb->{bwithdesc=List.map(map_instrf)b.desc})catch_all;}|TryCatch{label;typ;block;arms}->TryCatch{label;typ;block={blockwithdesc=List.map(map_instrf)block.desc};arms=List.map(funa->{awitharm_body={a.arm_bodywithdesc=List.map(map_instrf)a.arm_body.desc;};})arms;}|(Unreachable|Nop|Hole|Null|Get_|Path_|Char_|String_|Int_|Float_|StructDefault_)asx->x|Set(idx,op,v)->Set(idx,op,map_instrfv)|Tee(idx,v)->Tee(idx,map_instrfv)|Labelled(l,v)->Labelled(l,map_instrfv)|Call(target,args)->Call(map_instrftarget,List.map(map_instrf)args)|TailCall(target,args)->TailCall(map_instrftarget,List.map(map_instrf)args)|Cast(v,t)->Cast(map_instrfv,t)|CastDesc(v,t,d)->CastDesc(map_instrfv,t,map_instrfd)|Test(v,t)->Test(map_instrfv,t)|NonNullv->NonNull(map_instrfv)|Struct(idx,fields)->Struct(idx,List.map(fun(i,v)->(i,Option.map(map_instrf)v))fields)|StructDesc(d,fields)->StructDesc(map_instrfd,List.map(fun(i,v)->(i,Option.map(map_instrf)v))fields)|StructDefaultDescd->StructDefaultDesc(map_instrfd)|StructGet(v,idx)->StructGet(map_instrfv,idx)|GetDescriptorv->GetDescriptor(map_instrfv)|StructSet(v,idx,w)->StructSet(map_instrfv,idx,map_instrfw)|Array(idx,len,init)->Array(idx,map_instrflen,map_instrfinit)|ArrayDefault(idx,len)->ArrayDefault(idx,map_instrflen)|ArrayFixed(idx,elems)->ArrayFixed(idx,List.map(map_instrf)elems)|ArraySegment(idx,seg,off,len)->ArraySegment(idx,seg,map_instrfoff,map_instrflen)|ArrayGet(arr,idx)->ArrayGet(map_instrfarr,map_instrfidx)|ArraySet(arr,idx,val_)->ArraySet(map_instrfarr,map_instrfidx,map_instrfval_)|BinOp(op,l,r)->BinOp(op,map_instrfl,map_instrfr)|UnOp(op,v)->UnOp(op,map_instrfv)|Let(bindings,body)->Let(bindings,Option.map(map_instrf)body)|Br(label,v)->Br(label,Option.map(map_instrf)v)|Br_if(label,v)->Br_if(label,map_instrfv)|Hinted(h,i)->Hinted(h,map_instrfi)|On(i,h)->On(map_instrfi,h)|Br_table(labels,v)->Br_table(labels,map_instrfv)|Dispatch{index;cases;default;arms}->Dispatch{index=map_instrfindex;cases;default;arms=List.map(fun(l,body)->(l,{bodywithdesc=List.map(map_instrf)body.desc}))arms;}|Match{scrutinee;arms;default}->Match{scrutinee=map_instrfscrutinee;arms=List.map(fun(pat,body)->(pat,{bodywithdesc=List.map(map_instrf)body.desc}))arms;default={defaultwithdesc=List.map(map_instrf)default.desc};}|Br_on_null(label,v)->Br_on_null(label,map_instrfv)|Br_on_non_null(label,v)->Br_on_non_null(label,map_instrfv)|Br_on_cast(label,t,v)->Br_on_cast(label,t,map_instrfv)|Br_on_cast_fail(label,t,v)->Br_on_cast_fail(label,t,map_instrfv)|Br_on_cast_desc_eq(label,t,v,d)->Br_on_cast_desc_eq(label,t,map_instrfv,map_instrfd)|Br_on_cast_desc_eq_fail(label,t,v,d)->Br_on_cast_desc_eq_fail(label,t,map_instrfv,map_instrfd)|Throw(idx,args)->Throw(idx,List.map(map_instrf)args)|ThrowRefv->ThrowRef(map_instrfv)|ContNew(ct,v)->ContNew(ct,map_instrfv)|ContBind(src,dst,args)->ContBind(src,dst,List.map(map_instrf)args)|Suspend(tag,args)->Suspend(tag,List.map(map_instrf)args)|Resume(ct,handlers,args)->Resume(ct,handlers,List.map(map_instrf)args)|ResumeThrow(ct,tag,handlers,args)->ResumeThrow(ct,tag,handlers,List.map(map_instrf)args)|ResumeThrowRef(ct,handlers,args)->ResumeThrowRef(ct,handlers,List.map(map_instrf)args)|Switch(ct,tag,args)->Switch(ct,tag,List.map(map_instrf)args)|Returnv->Return(Option.map(map_instrf)v)|Sequenceinstrs->Sequence(List.map(map_instrf)instrs)|Select(cond,t,e)->Select(map_instrfcond,map_instrft,map_instrfe)|If_annotation{cond;then_body;else_body}->If_annotation{cond;then_body={then_bodywithdesc=List.map(map_instrf)then_body.desc};else_body=Option.map(funb->{bwithdesc=List.map(map_instrf)b.desc})else_body;}in{desc;info=finstr.info}(* The instructions immediately nested within [i] (its operands and block
bodies), in no particular evaluation order. A punned struct field ([None]) is
a leaf and contributes nothing. *)letsub_instrs(i:(_Ast.instr_desc,_)Ast.annotated)=matchi.descwith|Block{block;_}|Loop{block;_}|TryTable{block;_}->block.desc|While{cond;step;block;_}->(cond::Option.to_liststep)@block.desc|If{cond;if_block;else_block;_}->(cond::if_block.desc)@Option.fold~none:[]~some:(funb->b.desc)else_block|Try{block;catches;catch_all;_}->block.desc@List.concat_map(fun(_,b)->b.desc)catches@Option.fold~none:[]~some:(funb->b.desc)catch_all|TryCatch{block;arms;_}->block.desc@List.concat_map(funa->a.arm_body.desc)arms|If_annotation{then_body;else_body;_}->(then_body.desc@matchelse_bodywithSomeb->b.desc|None->[])|Sequencel|ArrayFixed(_,l)->l|Dispatch{index;arms;_}->index::List.concat_map(fun(_,b)->b.desc)arms|Match{scrutinee;arms;default}->(scrutinee::List.concat_map(fun(_,b)->b.desc)arms)@default.desc|ContBind(_,_,l)|Suspend(_,l)|Resume(_,_,l)|ResumeThrow(_,_,_,l)|ResumeThrowRef(_,_,l)|Switch(_,_,l)|Throw(_,l)->l|Call(a,l)|TailCall(a,l)->a::l|Struct(_,l)->List.filter_mapsndl|StructDesc(d,l)->List.filter_mapsndl@[d]|CastDesc(a,_,b)|Br_on_cast_desc_eq(_,_,a,b)|Br_on_cast_desc_eq_fail(_,_,a,b)|BinOp(_,a,b)|Array(_,a,b)|ArraySegment(_,_,a,b)|ArrayGet(a,b)|StructSet(a,_,b)->[a;b]|ArraySet(a,b,c)|Select(a,b,c)->[a;b;c]|Set(_,_,i)|Tee(_,i)|Labelled(_,i)|Cast(i,_)|Test(i,_)|NonNulli|UnOp(_,i)|StructGet(i,_)|GetDescriptori|StructDefaultDesci|ArrayDefault(_,i)|Br_if(_,i)|Hinted(_,i)|On(i,_)|Br_table(_,i)|Br_on_null(_,i)|Br_on_non_null(_,i)|Br_on_cast(_,_,i)|Br_on_cast_fail(_,_,i)|ThrowRefi|ContNew(_,i)->[i]|Let(_,o)|Br(_,o)|Returno->Option.to_listo|Unreachable|Nop|Hole|Null|Get_|Path_|Char_|String_|Int_|Float_|StructDefault_->[]letreciter_instrfi=fi;List.iter(iter_instrf)(sub_instrsi)(* Lower a [dispatch] to the conventional dense-switch shape: one nested void
block per case, the [br_table] in the innermost block, and each case body
placed just after its block. Branching to case [cᵢ] exits its block and runs
[cᵢ]'s body, then falls through into the enclosing cases.
Arms are listed in fall-through order — the first arm innermost, falling
through into the next, and so on — which is the reverse of the block nesting:
the *last* arm is outermost and its body trails the whole structure (hence
the result is an instruction *list*: the outermost block followed by that
trailing body). So we build from the reversed arm list, outermost first.
This is the exact inverse of {!Recover_dispatch}, so a recovered dispatch
re-lowers to the original blocks byte-for-byte. Every synthesised block and
the [br_table] carry [block_info]; the index and case bodies keep their own. *)letlower_dispatch~block_info~index~cases~default~arms=letmkdesc={desc;info=block_info}inletvoid={params=[||];results=[||]}inletbr=mk(Br_table(cases@[default],index))inletrecbuild=function|[(c,_)]->(* innermost case block holds just the [br_table] *)mk(Block{label=Somec;typ=void;block=no_loc[br]})|(c,_)::((_,next_body)::_asrest)->mk(Block{label=Somec;typ=void;block=no_loc(buildrest::next_body.desc);})|[]->brinmatchList.revarmswith|[]->[br]|(_,outer_body)::_asrev_arms->buildrev_arms::outer_body.desc(* Label of the [loop] a label-less [while]/[do]-[while] lowers to during type
checking. The [#] is not a Wax identifier character, so it can never clash
with a source label nor be the target of a user [br], keeping the body's
branches well resolved. (Wasm conversion instead resolves the label to a
readable [loop]/[loopN] before lowering — that name is what reaches emitted
Wat — so this synthetic one only ever labels the discarded type-check
lowering; see [To_wasm].) *)letsynthetic_loop_label="#loop"(* Lower a structured [try] to the [try_table]-plus-block-ladder shape: one
block per arm (the first arm innermost) plus the [join] block, the
[try_table] innermost with one catch clause per arm branching to its arm's
block, and each arm body as the trailing code just after its block — so an
arm's completion falls into the next arm, and the last arm's falls out of
the [join] as the try's value. The body's normal completion escapes past
all arms with the one implicit [br 'join], carrying the try's value. Each
arm block's result type is the arm's entry stack ([arm_types], the tag's
payload plus the [&exn] for a [&] arm, filled by the typer). [join] is the
try's own label when it has one. This is the exact inverse of
{!Recover_trycatch} and is used by Wax-to-Wasm conversion (type checking
types the structured node directly). *)letlower_trycatch~block_info~join~arm_labels~typ~block~arms=letmkdesc={desc;info=block_info}inletcatches=List.map2(funlarm->match(arm.arm_tag,arm.arm_ref)with|Somet,false->Catch(t,l)|Somet,true->CatchRef(t,l)|None,false->CatchAlll|None,true->CatchAllRefl)arm_labelsarmsinlettrytable=mk(TryTable{label=None;typ;catches;block})inletinner=iftyp.results=[||]then[trytable;mk(Br(join,None))]else[mk(Br(join,Sometrytable))]inletrecwrapinnerlabelsarms=match(labels,arms)with|[],[]->inner|l::labels',arm::arms'->letblk=mk(Block{label=Somel;typ={params=[||];results=arm.arm_types};block=no_locinner;})inwrap(blk::arm.arm_body.desc)labels'arms'|_->assertfalseinmk(Block{label=Somejoin;typ;block=no_loc(wrapinnerarm_labelsarms)})(* Lower a leading-test [while C { B }] to ['L: loop { if C { B; br 'L; } }]:
each iteration re-tests [C] and, while it holds, runs the body and branches
back; a false test falls out of the loop. Exact inverse of the [while] case
of {!Recover_loops}. *)(* With a Zig-style continue-expression [step] the step must run at the end of
every iteration, including when the body branches to the loop label
([continue]). When the loop is labelled — so a [continue] can target it — the
body is wrapped in a block carrying the user's label: [br 'L] then exits the
block, runs the step, and takes the back-edge, so a [continue] runs the step
before re-testing. This uses one fresh loop label ([fresh_loop]) for the
back-edge. An unlabelled stepped loop cannot be continued, so the step is
simply appended to the body (byte-identical to a trailing-step [while]). *)letlower_while~block_info~fresh_loop~label~cond~step~block=letmkdesc={desc;info=block_info}inletvoid={params=[||];results=[||]}inletif_condbody=mk(If{label=None;typ=void;cond;if_block=no_locbody;else_block=None;})inmatch(step,label)with|Somestep,Someblk_l->letbody_block=mk(Block{label=Someblk_l;typ=void;block=no_locblock})in[mk(Loop{label=Somefresh_loop;typ=void;block=no_loc[if_cond[body_block;step;mk(Br(fresh_loop,None))]];});]|_->letl=Option.valuelabel~default:fresh_loopinlettail=Option.to_liststep@[mk(Br(l,None))]in[mk(Loop{label=Somel;typ=void;block=no_loc[if_cond(block@tail)];});](* Lower a [match] to the conventional nested type-test ladder that compilers
emit (and that hand-written GC code uses): one nested block per arm, plus an
outer void [escape] block. The scrutinee is evaluated *once* and threaded
through a chain of [br_on_cast] (or [br_on_null] for a [null] arm) sitting in
the innermost block — each test, on success, branches *out* to its arm's block
carrying the narrowed value, and on failure leaves the (progressively
narrowed) value for the next test. The first arm is innermost: branching to it
exits one block and runs its body (placed just after the block), so arm [i]'s
body sits in arm [i+1]'s block — and the last arm's body sits in the [escape]
block.
A bound cast arm binds its block's result ([let x = …]); an unbound cast arm
drops it; a [null] arm's block is void. After every test fails the innermost
block drops the final value and [br escape]s past all the arm bodies; the
[default] then follows the (void) [escape] block as trailing code. So, as
before, each arm body must leave the [match] (diverge) while the default /
no-match path falls through after the [match] — and its stack effect (in
particular, divergence) propagates, exactly as the trailing first-arm body
does for {!lower_dispatch}.
[labels] supplies [n+1] fresh block labels: one per arm (in order) then the
[escape] label. This is the exact inverse of {!Recover_match}: a recovered
match re-lowers to the original blocks. *)letlower_match~block_info~labels~scrutinee~arms~default=letmkdesc={desc;info=block_info}inletvoid={params=[||];results=[||]}inletres=function|MatchCast(_,rt)->{params=[||];results=[|Refrt|]}|MatchNull->voidin(* Consume a wrapped block's result for arm [pat], then run [body]. *)letconsumeblkpatbody=matchpatwith|MatchCast((Some_asbind),rt)->mk(Let([(bind,Some(Refrt))],Someblk))::body|MatchCast(None,_)->mk(Let([(None,None)],Someblk))::body|MatchNull->blk::bodyinmatcharmswith|[]->default.desc|(p0,b0)::rest_arms->letrecunsnoc=function|[x]->([],x)|x::r->letinit,last=unsnocrin(x::init,last)|[]->assertfalseinletarm_labels,escape=unsnoclabelsinletl0,rest_labels=matcharm_labelswithx::r->(x,r)|[]->assertfalsein(* Threaded test chain (innermost operand the scrutinee, first test
innermost): [br_on_cast L0; br_on_cast L1; …]. *)letchain=List.fold_left2(funoperandlbl(pat,_)->matchpatwith|MatchCast(_,rt)->mk(Br_on_cast(lbl,rt,operand))|MatchNull->mk(Br_on_null(lbl,operand)))scrutineearm_labelsarmsin(* Innermost block drops the final fall-through value then escapes; the
default follows the [escape] block as trailing code. *)letinner=[mk(Let([(None,None)],Somechain));mk(Br(escape,None))]inletblock_l0=mk(Block{label=Somel0;typ=resp0;block=no_locinner})in(* Wrap outward: each block holds the previous block (its result consumed
for the previous arm) followed by that arm's body. *)letrecwrapprev_blockprev_patprev_bodylabelsarms=match(labels,arms)with|[],[]->mk(Block{label=Someescape;typ=void;block=no_loc(consumeprev_blockprev_patprev_body);})::default.desc|lbl::labels',(pat,body)::arms'->letblk=mk(Block{label=Somelbl;typ=respat;block=no_loc(consumeprev_blockprev_patprev_body);})inwrapblkpatbody.desclabels'arms'|_->assertfalseinwrapblock_l0p0b0.descrest_labelsrest_armsletrecmap_modulefieldffield=matchfieldwith|Typet->Typet|Module_annotationa->Module_annotationa(* Imports carry no instructions, so the info type is free to change. *)|Import{module_;decl}->Import{module_;decl}|Import_group{module_;decls}->Import_group{module_;decls}|Tagt->Tagt|Func({body=s,instrs;_}asfunc)->Func{funcwithbody=(s,List.map(map_instrf)instrs)}|Globalg->Global{gwithdef=map_instrfg.def}|Memorym->Memory{mwithdata=List.map(fund->{dwithoffset=map_instrfd.offset})m.data;}|Data({mode;_}asd)->Data{dwithmode=(matchmodewith|Passive->Passive|Active(mem,off)->Active(mem,map_instrfoff));}|Table({init;_}ast)->Table{twithinit=Option.map(map_instrf)init}|Elem({mode;init;_}ase)->Elem{ewithmode=(matchmodewith|EPassive->EPassive|EActive(tab,off)->EActive(tab,map_instrfoff));init=List.map(map_instrf)init;}|Conditional{cond;then_fields;else_fields}->letmap_fieldsb={bwithdesc=List.map(funa->{awithdesc=map_modulefieldfa.desc})b.desc;}inConditional{cond;then_fields=map_fieldsthen_fields;else_fields=Option.mapmap_fieldselse_fields;}letreciter_fieldsfl=List.iter(funfield->ffield;matchfield.descwith|Conditional{then_fields;else_fields;_}->iter_fieldsfthen_fields.desc;Option.iter(funb->iter_fieldsfb.desc)else_fields|_->())lletiter_module_instrfm=iter_fields(funfield->letroots=matchfield.descwith|Func{body=_,instrs;_}->instrs|Global{def;_}->[def]|Memory{data;_}->List.map(fund->d.offset)data|Data{mode=Active(_,off);_}->[off]|Table{init;_}->Option.to_listinit|Elem{mode;init;_}->(init@matchmodewithEActive(_,off)->[off]|EPassive->[])(* No instructions of their own; a [Conditional]'s nested fields reach
[f] via [iter_fields]' own recursion. *)|Data{mode=Passive;_}|Type_|Tag_|Import_|Import_group_|Module_annotation_|Conditional_->[]inList.iter(iter_instrf)roots)m(* The precedence class of a binary operator. Shared by the [precedence] lint
(see [Typing.lint_precedence]) and the Wax printer (see [Output]), so the
parentheses the printer adds match exactly the mixes the lint would flag. *)typebinop_kind=[`Shift|`Arith|`Bitwise|`Comparison]letbinop_kind:Ast.binop->binop_kind=function|Shl|Shr_->`Shift|Add|Sub|Mul|Div_|Rem_->`Arith|And|Or|Xor->`Bitwise|Eq|Ne|Lt_|Gt_|Le_|Ge_->`Comparison(* Whether a binary operator of kind [outer] applied to an operand that is
itself a binary operator of kind [inner] is a precedence footgun: a shift
mixed with arithmetic, or a comparison mixed with a bitwise operator. In such
a mix the inner (operand) operator is the tighter-binding one, and a reader
coming from C (whose table differs — see docs/src/language.md) may misgroup
it. The relation is symmetric. *)letconfusing_precedence(outer:binop_kind)(inner:binop_kind)=match(outer,inner)with|`Shift,`Arith|`Arith,`Shift->true|`Comparison,`Bitwise|`Bitwise,`Comparison->true|_->false(* The name an imported entity is bound to in Wasm: the name-only
[#[import = "name"]] override if present, else the Wax name [id]. *)letimport_name(decl:import_decl)=letoverride=List.find_map(fun(k,v,_)->ifk<>"import"thenNoneelsematchvwith|Some{desc=String(_,s);info}->Some{desc=s;info}|_->None)decl.attributesinOption.valueoverride~default:decl.id