123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179(* Single source of truth for the atomic memory instructions (the threads
proposal): the mapping between an [Ast.atomicop], its WAT mnemonic, its
[0xFE]-prefix sub-opcode, its natural alignment and its stack signature.
[Atomic.fence] is handled separately (it has no memory operand). *)openAstlettype_str=function`I32->"i32"|`I64->"i64"letwidth_str=function`I8->"8"|`I16->"16"|`I32->"32"letrmw_str=function|AtomicAdd->"add"|AtomicSub->"sub"|AtomicAnd->"and"|AtomicOr->"or"|AtomicXor->"xor"|AtomicXchg->"xchg"|AtomicCmpxchg->"cmpxchg"(* The WAT mnemonic of an atomic op. *)letname=function|AtomicNotify->"memory.atomic.notify"|AtomicWait`I32->"memory.atomic.wait32"|AtomicWait`I64->"memory.atomic.wait64"|AtomicLoad(t,None)->type_strt^".atomic.load"|AtomicLoad(t,Somew)->type_strt^".atomic.load"^width_strw^"_u"|AtomicStore(t,None)->type_strt^".atomic.store"|AtomicStore(t,Somew)->type_strt^".atomic.store"^width_strw|AtomicRmw(op,t,None)->type_strt^".atomic.rmw."^rmw_strop|AtomicRmw(op,t,Somew)->type_strt^".atomic.rmw"^width_strw^"."^rmw_strop^"_u"(* The (type, width) sequence shared by every load / store / rmw sub-opcode
block, in binary order: i32-full, i64-full, then the narrow accesses. *)letvariants=[(`I32,None);(`I64,None);(`I32,Some`I8);(`I32,Some`I16);(`I64,Some`I8);(`I64,Some`I16);(`I64,Some`I32);]letrmw_ops=[AtomicAdd;AtomicSub;AtomicAnd;AtomicOr;AtomicXor;AtomicXchg;AtomicCmpxchg;](* Every [(sub-opcode, op)] pair, generated from the regular block layout. *)lettable=[(0x00,AtomicNotify);(0x01,AtomicWait`I32);(0x02,AtomicWait`I64)]@List.mapi(funi(t,w)->(0x10+i,AtomicLoad(t,w)))variants@List.mapi(funi(t,w)->(0x17+i,AtomicStore(t,w)))variants@List.concat(List.mapi(funjop->letbase=0x1E+(j*7)inList.mapi(funi(t,w)->(base+i,AtomicRmw(op,t,w)))variants)rmw_ops)letall=List.mapsndtableletby_opcode=Hashtbl.create128let()=List.iter(fun(code,op)->Hashtbl.replaceby_opcodecodeop)tableletopcodeop=fst(List.find(fun(_,o)->o=op)table)letof_opcodecode=Hashtbl.find_optby_opcodecode(* The Wax surface: a method name carries the *access width* only
([atomic_load16], [atomic_rmw_add8]); the i32/i64 value type is resolved from
the operand and result types during typing, mirroring the plain scalar
accesses ([load16(p) as i64_u]). A name therefore denotes a *family* of
concrete ops; [atomic_wait32]/[atomic_wait64] and [atomic_notify] resolve
from the name alone. *)typewidth=[`W8|`W16|`W32|`W64]typefamily=|Loadofwidth|Storeofwidth|Rmwofatomic_rmwop*width|Waitof[`I32|`I64]|Notifyletwidths:widthlist=[`W8;`W16;`W32;`W64]letwidth_name:width->string=function|`W8->"8"|`W16->"16"|`W32->"32"|`W64->"64"(* Number of bytes a family accesses — the width from the name, independent of
which i32/i64 value type the operands select; its base-2 logarithm is the
required (exact) alignment. *)letwidth_bytes:width->int=function|`W8->1|`W16->2|`W32->4|`W64->8letfamily_bytes=function|Loadw|Storew|Rmw(_,w)->width_bytesw|Wait`I32->4|Wait`I64->8|Notify->4(* Every Wax method family, in completion order: loads, stores, RMWs, then
wait/notify. *)letfamilies=List.map(funw->Loadw)widths@List.map(funw->Storew)widths@List.concat_map(funop->List.map(funw->Rmw(op,w))widths)rmw_ops@[Wait`I32;Wait`I64;Notify](* The Wax method spelling on a memory receiver: [atomic_<op><width>]. *)letmethod_name=function|Loadw->"atomic_load"^width_namew|Storew->"atomic_store"^width_namew|Rmw(op,w)->"atomic_rmw_"^rmw_strop^width_namew|Wait`I32->"atomic_wait32"|Wait`I64->"atomic_wait64"|Notify->"atomic_notify"letby_method=Hashtbl.create64let()=List.iter(funf->Hashtbl.replaceby_method(method_namef)f)familiesletof_method_namen=Hashtbl.find_optby_methodn(* The family a concrete op belongs to (its width is the access width). *)letfamilyop=letwidthtw:width=matchwwith|Some`I8->`W8|Some`I16->`W16|Some`I32->`W32|None->(matchtwith`I32->`W32|`I64->`W64)inmatchopwith|AtomicNotify->Notify|AtomicWaitt->Waitt|AtomicLoad(t,w)->Load(widthtw)|AtomicStore(t,w)->Store(widthtw)|AtomicRmw(op,t,w)->Rmw(op,widthtw)(* Number of bytes accessed, whose base-2 logarithm is the required (exact)
alignment. *)letaccess_bytes=function|AtomicNotify|AtomicWait`I32->4|AtomicWait`I64->8|AtomicLoad(t,w)|AtomicStore(t,w)|AtomicRmw(_,t,w)->(matchwwith|Some`I8->1|Some`I16->2|Some`I32->4|None->(matchtwith`I32->4|`I64->8))letnatural_align_log2op=matchaccess_bytesopwith1->0|2->1|4->2|_->3(* Stack signature after the address operand (which has the memory's address
type): the remaining operands and the results. *)letsignatureop:[`I32|`I64]list*[`I32|`I64]list=matchopwith|AtomicNotify->([`I32],[`I32])|AtomicWait`I32->([`I32;`I64],[`I32])|AtomicWait`I64->([`I64;`I64],[`I32])|AtomicLoad(t,_)->([],[t])|AtomicStore(t,_)->([t],[])|AtomicRmw(AtomicCmpxchg,t,_)->([t;t],[t])|AtomicRmw(_,t,_)->([t],[t])