wasm_opcodes/
lib.rs

1#![recursion_limit = "8192"]
2
3//! Implementing something on top of [`wasmparser`] is slightly tedious due to the number of operations that it can parse, usually
4//! resulting in either a huge match statement, or several match statements which each process some subset of the operations and
5//! then have a catchall `unreachable!()` statement. This crate provides a middleground: a hierarchy of operations, split by proposal,
6//! allowing you to exhaustively match firstly on the proposal, and then secondly on the operation.
7//!
8//! To start, use [`OperatorByProposal::from`]`(op: wasmparser::Operator)`, or the equivalent `Into` implementation.
9//!
10//! # Versioning
11//!
12//! This crate aims to match versions with [`wasmparser`], since the code in this crate is generated from wasmparser's supported
13//! operations.
14
15use wasmparser::for_each_operator;
16
17macro_rules! define_opcode {
18    ($(@$proposal:ident $op:ident $({ $($payload:tt)* })? => $visit:ident)*) => {
19        /// All possible WebAssembly instruction opcodes across all proposals supported by [`wasmparser`].
20        ///
21        /// Use either [`OpCode::from_operator`] to extract the opcode from a [`wasmparser::Operator`], or
22        /// [`OperatorByProposal::opcode`] to extract the opcode from an [`OperatorByProposal`].
23        #[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
24        #[allow(missing_docs)]
25        pub enum OpCode {
26            $(
27                $op,
28            )*
29        }
30
31        impl OpCode {
32            pub fn from_operator<'a>(op: &wasmparser::Operator<'a>) -> Self {
33                match op {
34                    $(
35                        wasmparser::Operator::$op{..} => Self::$op,
36                    )*
37                }
38            }
39        }
40    }
41}
42for_each_operator!(define_opcode);
43
44/// A hierarchy of all WebAssembly operations, split by proposal.
45pub enum OperatorByProposal<'a> {
46    ControlFlow(proposals::ControlFlowOperator<'a>),
47    MVP(proposals::MVPOperator),
48    Exceptions(proposals::ExceptionsOperator),
49    TailCall(proposals::TailCallOperator),
50    ReferenceTypes(proposals::ReferenceTypesOperator),
51    SignExtension(proposals::SignExtensionOperator),
52    SaturatingFloatToInt(proposals::SaturatingFloatToIntOperator),
53    BulkMemory(proposals::BulkMemoryOperator),
54    Threads(proposals::ThreadsOperator),
55    SIMD(proposals::SIMDOperator),
56    RelaxedSIMD(proposals::RelaxedSIMDOperator),
57    FunctionReferences(proposals::FunctionReferencesOperator),
58    MemoryControl(proposals::MemoryControlOperator),
59    GC(proposals::GCOperator),
60}
61
62macro_rules! impl_op_by_proposal {
63    ($(@$proposal:ident $op:ident $({ $($field:ident : $field_ty:ty),* $(,)? })? => $visit:ident)*) => {
64        impl<'a> From<wasmparser::Operator<'a>> for OperatorByProposal<'a> {
65            fn from(op: wasmparser::Operator<'a>) -> Self {
66                match op {
67                    $(
68                        wasmparser::Operator::$op $({ $($field),* })* => paste::paste!{ proposals::[< make_op_by_proposal_ $op:snake >] ( $($($field),* )* ) },
69                    )*
70                }
71            }
72        }
73    }
74}
75for_each_operator!(impl_op_by_proposal);
76
77impl<'a> From<OperatorByProposal<'a>> for wasmparser::Operator<'a> {
78    fn from(op: OperatorByProposal<'a>) -> Self {
79        match op {
80            OperatorByProposal::ControlFlow(op) => op.into(),
81            OperatorByProposal::MVP(op) => op.into(),
82            OperatorByProposal::Exceptions(op) => op.into(),
83            OperatorByProposal::TailCall(op) => op.into(),
84            OperatorByProposal::ReferenceTypes(op) => op.into(),
85            OperatorByProposal::SignExtension(op) => op.into(),
86            OperatorByProposal::SaturatingFloatToInt(op) => op.into(),
87            OperatorByProposal::BulkMemory(op) => op.into(),
88            OperatorByProposal::Threads(op) => op.into(),
89            OperatorByProposal::SIMD(op) => op.into(),
90            OperatorByProposal::RelaxedSIMD(op) => op.into(),
91            OperatorByProposal::FunctionReferences(op) => op.into(),
92            OperatorByProposal::MemoryControl(op) => op.into(),
93            OperatorByProposal::GC(op) => op.into(),
94        }
95    }
96}
97
98pub mod proposals {
99    use super::*;
100
101    /// Defines a new macro that filters operations by a proposal
102    macro_rules! filter_operators {
103        ($d:tt $macro_name:ident (@ $filter_token:tt $(, !$filter_op:tt)* $(,)?) | $called_macro:ident($($args:tt)*)) => {
104            macro_rules! $macro_name {
105                ((munch) { $d ($d filtered:tt)* }) => {
106                    $called_macro!{$($args)* $d ($d filtered)*}
107                };
108                $(
109                    ((munch) { $d ($d filtered:tt)* } @$d proposal:ident $filter_op $d ({ $d ($d payload:tt)* })? => $d visit:ident $d ($d others:tt)*) => {
110                        $macro_name!{(munch) { $d ($d filtered)* } $d ($d others)*}
111                    };
112                )*
113                ((munch) { $d ($d filtered:tt)* } @$filter_token $d op:ident $d ({ $d ($d payload:tt)* })? => $d visit:ident $d ($d others:tt)*) => {
114                    $macro_name!{(munch) { $d ($d filtered)* @$filter_token $d op $d ({ $d ($payload)* })? => $d visit } $d ($d others)*}
115                };
116                ((munch) { $d ($d filtered:tt)* } @$d proposal:ident $d op:ident $d ({ $d ($d payload:tt)* })? => $d visit:ident $d ($d others:tt)*) => {
117                    $macro_name!{(munch) { $d ($d filtered)* } $d ($d others)*}
118                };
119                ($d ($d others:tt)*) => {
120                    $macro_name!{(munch) { } $d ($d others)*}
121                }
122            }
123        }
124    }
125
126    macro_rules! define_make_operator_fn {
127        ($enum_name:ident( $struct_name:ident :: $op:ident $({ $($field:ident : $field_ty:ty),* $(,)? })? )) => {
128            paste::paste!{
129                #[inline(always)]
130                pub(crate) fn [< make_op_by_proposal_ $op:snake >] <'a> ( $($($field : $field_ty),* )* ) -> OperatorByProposal<'a> {
131                    OperatorByProposal::$enum_name(
132                        $struct_name::$op {$($($field),* )*}
133                    )
134                }
135            }
136        }
137    }
138
139    /// Defines a struct with some identity, to be used with the filter to have a set of only some opcodes
140    macro_rules! define_proposal_operator {
141        ($struct_name:ident, $enum_name:ident $(@$proposal:ident $op:ident $({ $($field:ident : $field_ty:ty),* $(,)? })? => $visit:ident)*) => {
142            #[derive(Clone, Debug)]
143            #[doc = concat!("A subset of WebAssembly operations given by the ", stringify!($enum_name), " proposal")]
144            pub enum $struct_name {
145                $(
146                    $op $({ $($field : $field_ty,)* })*,
147                )*
148            }
149
150            impl $struct_name {
151                pub fn opcode(&self) -> OpCode {
152                    match &self {
153                        $(
154                            Self::$op { .. } => OpCode::$op,
155                        )*
156                    }
157                }
158            }
159
160            impl<'a> From<$struct_name> for wasmparser::Operator<'a> {
161                fn from(op: $struct_name) -> Self {
162                    match op {
163                        $(
164                            $struct_name::$op $({ $($field,)* })* => wasmparser::Operator::$op $({ $($field,)* })*,
165                        )*
166                    }
167                }
168            }
169
170            $(
171                define_make_operator_fn! { $enum_name ( $struct_name :: $op $({ $($field : $field_ty),* })* ) }
172            )*
173        }
174    }
175
176    filter_operators!($ filter_define_mvp(@mvp, 
177        !End,
178        !Block,
179        !Loop,
180        !If,
181        !Else,
182        !Br,
183        !BrIf,
184        !BrTable,
185        !Return,
186        !Call,
187        !CallIndirect,
188    ) | define_proposal_operator(MVPOperator, MVP));
189    for_each_operator!(filter_define_mvp);
190    filter_operators!($ filter_define_exceptions(@exceptions) | define_proposal_operator(ExceptionsOperator, Exceptions));
191    for_each_operator!(filter_define_exceptions);
192    filter_operators!($ filter_define_tail_call(@tail_call) | define_proposal_operator(TailCallOperator, TailCall));
193    for_each_operator!(filter_define_tail_call);
194    filter_operators!($ filter_define_reference_types(@reference_types) | define_proposal_operator(ReferenceTypesOperator, ReferenceTypes));
195    for_each_operator!(filter_define_reference_types);
196    filter_operators!($ filter_define_sign_extension(@sign_extension) | define_proposal_operator(SignExtensionOperator, SignExtension));
197    for_each_operator!(filter_define_sign_extension);
198    filter_operators!($ filter_define_saturating_float_to_int(@saturating_float_to_int) | define_proposal_operator(SaturatingFloatToIntOperator, SaturatingFloatToInt));
199    for_each_operator!(filter_define_saturating_float_to_int);
200    filter_operators!($ filter_define_bulk_memory(@bulk_memory) | define_proposal_operator(BulkMemoryOperator, BulkMemory));
201    for_each_operator!(filter_define_bulk_memory);
202    filter_operators!($ filter_define_threads(@threads) | define_proposal_operator(ThreadsOperator, Threads));
203    for_each_operator!(filter_define_threads);
204    filter_operators!($ filter_define_simd(@simd) | define_proposal_operator(SIMDOperator, SIMD));
205    for_each_operator!(filter_define_simd);
206    filter_operators!($ filter_define_relaxed_simd(@relaxed_simd) | define_proposal_operator(RelaxedSIMDOperator, RelaxedSIMD));
207    for_each_operator!(filter_define_relaxed_simd);
208    filter_operators!($ filter_define_function_references(@function_references) | define_proposal_operator(FunctionReferencesOperator, FunctionReferences));
209    for_each_operator!(filter_define_function_references);
210    filter_operators!($ filter_define_memory_control(@memory_control) | define_proposal_operator(MemoryControlOperator, MemoryControl));
211    for_each_operator!(filter_define_memory_control);
212    filter_operators!($ filter_define_gc(@gc) | define_proposal_operator(GCOperator, GC));
213    for_each_operator!(filter_define_gc);
214
215    impl<'a> OperatorByProposal<'a> {
216        pub fn opcode(&self) -> OpCode {
217            match self {
218                Self::ControlFlow(op) => op.opcode(),
219                Self::MVP(op) => op.opcode(),
220                Self::Exceptions(op) => op.opcode(),
221                Self::TailCall(op) => op.opcode(),
222                Self::ReferenceTypes(op) => op.opcode(),
223                Self::SignExtension(op) => op.opcode(),
224                Self::SaturatingFloatToInt(op) => op.opcode(),
225                Self::BulkMemory(op) => op.opcode(),
226                Self::Threads(op) => op.opcode(),
227                Self::SIMD(op) => op.opcode(),
228                Self::RelaxedSIMD(op) => op.opcode(),
229                Self::FunctionReferences(op) => op.opcode(),
230                Self::MemoryControl(op) => op.opcode(),
231                Self::GC(op) => op.opcode(),
232            }
233        }
234    }
235
236    /// MVP operators which do some kind of control flow.
237    pub enum ControlFlowOperator<'a> {
238        End,
239        Block {
240            blockty: wasmparser::BlockType,
241        },
242        Loop {
243            blockty: wasmparser::BlockType,
244        },
245        If {
246            blockty: wasmparser::BlockType,
247        },
248        Else,
249        Br {
250            relative_depth: u32,
251        },
252        BrIf {
253            relative_depth: u32,
254        },
255        BrTable {
256            targets: wasmparser::BrTable<'a>,
257        },
258        Return,
259        Call {
260            function_index: u32,
261        },
262        CallIndirect {
263            type_index: u32,
264            table_index: u32,
265            table_byte: u8,
266        },
267    }
268
269    impl<'a> ControlFlowOperator<'a> {
270        pub fn opcode(&self) -> OpCode {
271            match self {
272                ControlFlowOperator::End => OpCode::End,
273                ControlFlowOperator::Block { .. } => OpCode::Block,
274                ControlFlowOperator::Loop { .. } => OpCode::Loop,
275                ControlFlowOperator::If { .. } => OpCode::If,
276                ControlFlowOperator::Else => OpCode::Else,
277                ControlFlowOperator::Br { .. } => OpCode::Br,
278                ControlFlowOperator::BrIf { .. } => OpCode::BrIf,
279                ControlFlowOperator::BrTable { .. } => OpCode::BrTable,
280                ControlFlowOperator::Return => OpCode::Return,
281                ControlFlowOperator::Call { .. } => OpCode::Call,
282                ControlFlowOperator::CallIndirect { .. } => OpCode::CallIndirect,
283            }
284        }
285    }
286
287    impl<'a> From<ControlFlowOperator<'a>> for wasmparser::Operator<'a> {
288        fn from(op: ControlFlowOperator<'a>) -> Self {
289            match op {
290                ControlFlowOperator::End => wasmparser::Operator::End,
291                ControlFlowOperator::Block { blockty } => wasmparser::Operator::Block { blockty },
292                ControlFlowOperator::Loop { blockty } => wasmparser::Operator::Loop { blockty },
293                ControlFlowOperator::If { blockty } => wasmparser::Operator::If { blockty },
294                ControlFlowOperator::Else => wasmparser::Operator::Else,
295                ControlFlowOperator::Br { relative_depth } => {
296                    wasmparser::Operator::Br { relative_depth }
297                }
298                ControlFlowOperator::BrIf { relative_depth } => {
299                    wasmparser::Operator::BrIf { relative_depth }
300                }
301                ControlFlowOperator::BrTable { targets } => {
302                    wasmparser::Operator::BrTable { targets }
303                }
304                ControlFlowOperator::Return => wasmparser::Operator::Return,
305                ControlFlowOperator::Call { function_index } => {
306                    wasmparser::Operator::Call { function_index }
307                }
308                ControlFlowOperator::CallIndirect {
309                    type_index,
310                    table_index,
311                    table_byte,
312                } => wasmparser::Operator::CallIndirect {
313                    type_index,
314                    table_index,
315                    table_byte,
316                },
317            }
318        }
319    }
320
321    // Custom add control flow operations to make processing easier
322    define_make_operator_fn! {ControlFlow(ControlFlowOperator::End)}
323    define_make_operator_fn! {ControlFlow(ControlFlowOperator::Block {
324        blockty: wasmparser::BlockType,
325    })}
326    define_make_operator_fn! {ControlFlow(ControlFlowOperator::Loop {
327        blockty: wasmparser::BlockType,
328    })}
329    define_make_operator_fn! {ControlFlow(ControlFlowOperator::If {
330        blockty: wasmparser::BlockType,
331    })}
332    define_make_operator_fn! {ControlFlow(ControlFlowOperator::Else)}
333    define_make_operator_fn! {ControlFlow(ControlFlowOperator::Br {
334        relative_depth: u32,
335    })}
336    define_make_operator_fn! {ControlFlow(ControlFlowOperator::BrIf {
337        relative_depth: u32,
338    })}
339    define_make_operator_fn! {ControlFlow(ControlFlowOperator::Return)}
340    define_make_operator_fn! {ControlFlow(ControlFlowOperator::Call {
341        function_index: u32,
342    })}
343    define_make_operator_fn! {ControlFlow(ControlFlowOperator::CallIndirect {
344        type_index: u32,
345        table_index: u32,
346        table_byte: u8,
347    })}
348    define_make_operator_fn! {ControlFlow(ControlFlowOperator::BrTable {
349        targets: wasmparser::BrTable<'a>,
350    })}
351}