Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/pulley/src/decode.rs
3054 views
1
//! Decoding support for pulley bytecode.
2
3
use crate::imms::*;
4
use crate::opcode::*;
5
use crate::regs::*;
6
use alloc::vec::Vec;
7
use core::convert::Infallible;
8
use core::ptr::NonNull;
9
use cranelift_bitset::ScalarBitSet;
10
use cranelift_bitset::scalar::ScalarBitSetStorage;
11
12
/// Either an `Ok(T)` or an `Err(DecodingError)`.
13
pub type Result<T, E = DecodingError> = core::result::Result<T, E>;
14
15
/// An error when decoding Pulley bytecode.
16
pub enum DecodingError {
17
/// Reached the end of the bytecode stream before we finished decoding a
18
/// single bytecode.
19
UnexpectedEof {
20
/// The position in the bytecode stream where this error occurred.
21
position: usize,
22
},
23
24
/// Found an invalid opcode.
25
InvalidOpcode {
26
/// The position in the bytecode stream where this error occurred.
27
position: usize,
28
/// The invalid opcode that was found.
29
code: u8,
30
},
31
32
/// Found an invalid extended opcode.
33
InvalidExtendedOpcode {
34
/// The position in the bytecode stream where this error occurred.
35
position: usize,
36
/// The invalid extended opcode that was found.
37
code: u16,
38
},
39
40
/// Found an invalid register.
41
InvalidReg {
42
/// The position in the bytecode stream where this error occurred.
43
position: usize,
44
/// The invalid register that was found.
45
reg: u8,
46
},
47
}
48
49
impl core::fmt::Debug for DecodingError {
50
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
51
core::fmt::Display::fmt(self, f)
52
}
53
}
54
55
impl core::fmt::Display for DecodingError {
56
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
57
match self {
58
Self::UnexpectedEof { position } => {
59
write!(f, "unexpected end-of-file at bytecode offset {position:#x}")
60
}
61
Self::InvalidOpcode { position, code } => {
62
write!(
63
f,
64
"found invalid opcode {code:#x} at bytecode offset {position:#x}"
65
)
66
}
67
Self::InvalidExtendedOpcode { position, code } => {
68
write!(
69
f,
70
"found invalid opcode {code:#x} at bytecode offset {position:#x}"
71
)
72
}
73
Self::InvalidReg { position, reg } => {
74
write!(
75
f,
76
"found invalid register {reg:#x} at bytecode offset {position:#x}"
77
)
78
}
79
}
80
}
81
}
82
83
#[cfg(feature = "std")]
84
impl std::error::Error for DecodingError {}
85
86
/// An abstraction over any kind of bytecode stream.
87
///
88
/// There are two primary implementations:
89
///
90
/// 1. `SafeBytecodeStream`: A thin wrapper around an index into a `&[u8]`. This
91
/// implementation is 100% safe code.
92
///
93
/// 2. `UnsafeBytecodeStream`: A thin wrapper over a raw pointer. This
94
/// implementation is wildly unsafe and will result in memory unsafety and
95
/// other terrors when given invalid bytecode, or even valid bytecode
96
/// encoding a program that itself does not preserve memory safety.
97
pub trait BytecodeStream: Copy {
98
/// The type of error that this bytecode stream produces on invalid
99
/// operations.
100
type Error;
101
102
/// Create an "unexpected end-of-stream" error at the current position.
103
fn unexpected_eof(&self) -> Self::Error;
104
105
/// Create an "invalid opcode" error at the current position.
106
fn invalid_opcode(&self, code: u8) -> Self::Error;
107
108
/// Create an "invalid extended opcode" error at the current position.
109
fn invalid_extended_opcode(&self, code: u16) -> Self::Error;
110
111
/// Create an "invalid register" error at the current position.
112
fn invalid_reg(&self, reg: u8) -> Self::Error;
113
114
/// Read `N` bytes from this bytecode stream, advancing the stream's
115
/// position at the same time.
116
fn read<const N: usize>(&mut self) -> Result<[u8; N], Self::Error>;
117
}
118
119
/// A 100% safe implementation of a bytecode stream.
120
///
121
/// This is a thin wrapper around an index into a `&[u8]`.
122
#[derive(Clone, Copy, Debug)]
123
pub struct SafeBytecodeStream<'a> {
124
bytecode: &'a [u8],
125
position: usize,
126
}
127
128
impl<'a> SafeBytecodeStream<'a> {
129
/// Create a new `SafeBytecodeStream` from the given slice and with an
130
/// initial position pointing at the start of the slice.
131
pub fn new(bytecode: &'a [u8]) -> Self {
132
Self {
133
bytecode,
134
position: 0,
135
}
136
}
137
138
/// Get this stream's current position within its underlying slice.
139
pub fn position(&self) -> usize {
140
self.position
141
}
142
143
/// Get this stream's underlying bytecode slice.
144
pub fn as_slice(&self) -> &[u8] {
145
&self.bytecode
146
}
147
}
148
149
impl BytecodeStream for SafeBytecodeStream<'_> {
150
fn read<const N: usize>(&mut self) -> Result<[u8; N], Self::Error> {
151
let (bytes, rest) = self
152
.bytecode
153
.split_first_chunk()
154
.ok_or_else(|| self.unexpected_eof())?;
155
self.bytecode = rest;
156
self.position += N;
157
Ok(*bytes)
158
}
159
160
type Error = DecodingError;
161
162
fn unexpected_eof(&self) -> Self::Error {
163
DecodingError::UnexpectedEof {
164
position: self.position,
165
}
166
}
167
168
fn invalid_opcode(&self, code: u8) -> Self::Error {
169
DecodingError::InvalidOpcode {
170
position: self.position - 1,
171
code,
172
}
173
}
174
175
fn invalid_extended_opcode(&self, code: u16) -> Self::Error {
176
DecodingError::InvalidExtendedOpcode {
177
position: self.position,
178
code,
179
}
180
}
181
182
fn invalid_reg(&self, reg: u8) -> Self::Error {
183
DecodingError::InvalidReg {
184
position: self.position,
185
reg,
186
}
187
}
188
}
189
190
/// An unsafe bytecode stream.
191
///
192
/// This is a wrapper over a raw pointer to bytecode somewhere in memory.
193
#[derive(Clone, Copy, Debug)]
194
pub struct UnsafeBytecodeStream(NonNull<u8>);
195
196
impl UnsafeBytecodeStream {
197
/// Construct a new `UnsafeBytecodeStream` pointing at the given PC.
198
///
199
/// # Safety
200
///
201
/// The given `pc` must point to valid Pulley bytecode, and it is the
202
/// caller's responsibility to ensure that the resulting
203
/// `UnsafeBytecodeStream` is only used to access the valid bytecode. For
204
/// example, if the current bytecode instruction unconditionally jumps to a
205
/// new PC, this stream must not be used to read just after the
206
/// unconditional jump instruction because there is no guarantee that that
207
/// memory is part of the bytecode stream or not.
208
pub unsafe fn new(pc: NonNull<u8>) -> Self {
209
UnsafeBytecodeStream(pc)
210
}
211
212
/// Get a new `UnsafeBytecodeStream` pointing at the bytecode that is at the
213
/// given relative offset from this stream's current position.
214
///
215
/// # Safety
216
///
217
/// Same as the `new` constructor. May only be used when it is guaranteed
218
/// that the address at `self._as_ptr() + offset` contains valid Pulley
219
/// bytecode.
220
pub unsafe fn offset(&self, offset: isize) -> Self {
221
UnsafeBytecodeStream(unsafe { NonNull::new_unchecked(self.0.as_ptr().offset(offset)) })
222
}
223
224
/// Get this stream's underlying raw pointer.
225
pub fn as_ptr(&self) -> NonNull<u8> {
226
self.0
227
}
228
}
229
230
impl BytecodeStream for UnsafeBytecodeStream {
231
fn read<const N: usize>(&mut self) -> Result<[u8; N], Self::Error> {
232
let bytes = unsafe { self.0.cast::<[u8; N]>().as_ptr().read() };
233
self.0 = unsafe { NonNull::new_unchecked(self.0.as_ptr().add(N)) };
234
Ok(bytes)
235
}
236
237
type Error = Infallible;
238
239
fn unexpected_eof(&self) -> Self::Error {
240
unsafe { crate::unreachable_unchecked() }
241
}
242
243
fn invalid_opcode(&self, _code: u8) -> Self::Error {
244
unsafe { crate::unreachable_unchecked() }
245
}
246
247
fn invalid_extended_opcode(&self, _code: u16) -> Self::Error {
248
unsafe { crate::unreachable_unchecked() }
249
}
250
251
fn invalid_reg(&self, _reg: u8) -> Self::Error {
252
unsafe { crate::unreachable_unchecked() }
253
}
254
}
255
256
/// Anything that can be decoded from a bytecode stream, e.g. opcodes,
257
/// immediates, registers, etc...
258
pub trait Decode: Sized {
259
/// Decode this type from the given bytecode stream.
260
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
261
where
262
T: BytecodeStream;
263
}
264
265
impl Decode for u8 {
266
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
267
where
268
T: BytecodeStream,
269
{
270
bytecode.read::<1>().map(|a| a[0])
271
}
272
}
273
274
impl Decode for u16 {
275
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
276
where
277
T: BytecodeStream,
278
{
279
Ok(u16::from_le_bytes(bytecode.read()?))
280
}
281
}
282
283
impl Decode for u32 {
284
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
285
where
286
T: BytecodeStream,
287
{
288
Ok(u32::from_le_bytes(bytecode.read()?))
289
}
290
}
291
292
impl Decode for u64 {
293
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
294
where
295
T: BytecodeStream,
296
{
297
Ok(u64::from_le_bytes(bytecode.read()?))
298
}
299
}
300
301
impl Decode for u128 {
302
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
303
where
304
T: BytecodeStream,
305
{
306
Ok(u128::from_le_bytes(bytecode.read()?))
307
}
308
}
309
310
impl Decode for i8 {
311
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
312
where
313
T: BytecodeStream,
314
{
315
bytecode.read::<1>().map(|a| a[0] as i8)
316
}
317
}
318
319
impl Decode for i16 {
320
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
321
where
322
T: BytecodeStream,
323
{
324
Ok(i16::from_le_bytes(bytecode.read()?))
325
}
326
}
327
328
impl Decode for i32 {
329
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
330
where
331
T: BytecodeStream,
332
{
333
Ok(i32::from_le_bytes(bytecode.read()?))
334
}
335
}
336
337
impl Decode for i64 {
338
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
339
where
340
T: BytecodeStream,
341
{
342
Ok(i64::from_le_bytes(bytecode.read()?))
343
}
344
}
345
346
impl Decode for i128 {
347
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
348
where
349
T: BytecodeStream,
350
{
351
Ok(i128::from_le_bytes(bytecode.read()?))
352
}
353
}
354
355
impl Decode for XReg {
356
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
357
where
358
T: BytecodeStream,
359
{
360
let byte = u8::decode(bytecode)?;
361
XReg::new(byte).ok_or_else(|| bytecode.invalid_reg(byte))
362
}
363
}
364
365
impl Decode for FReg {
366
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
367
where
368
T: BytecodeStream,
369
{
370
let byte = u8::decode(bytecode)?;
371
FReg::new(byte).ok_or_else(|| bytecode.invalid_reg(byte))
372
}
373
}
374
375
impl Decode for VReg {
376
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
377
where
378
T: BytecodeStream,
379
{
380
let byte = u8::decode(bytecode)?;
381
VReg::new(byte).ok_or_else(|| bytecode.invalid_reg(byte))
382
}
383
}
384
385
impl Decode for PcRelOffset {
386
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
387
where
388
T: BytecodeStream,
389
{
390
i32::decode(bytecode).map(|x| Self::from(x))
391
}
392
}
393
394
impl Decode for Opcode {
395
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
396
where
397
T: BytecodeStream,
398
{
399
let byte = u8::decode(bytecode)?;
400
match Opcode::new(byte) {
401
Some(v) => Ok(v),
402
None => Err(bytecode.invalid_opcode(byte)),
403
}
404
}
405
}
406
407
impl Decode for ExtendedOpcode {
408
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
409
where
410
T: BytecodeStream,
411
{
412
let word = u16::decode(bytecode)?;
413
match ExtendedOpcode::new(word) {
414
Some(v) => Ok(v),
415
None => Err(bytecode.invalid_extended_opcode(word)),
416
}
417
}
418
}
419
420
impl<D: Reg, S1: Reg, S2: Reg> Decode for BinaryOperands<D, S1, S2> {
421
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
422
where
423
T: BytecodeStream,
424
{
425
u16::decode(bytecode).map(|bits| Self::from_bits(bits))
426
}
427
}
428
429
impl<D: Reg, S1: Reg> Decode for BinaryOperands<D, S1, U6> {
430
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
431
where
432
T: BytecodeStream,
433
{
434
u16::decode(bytecode).map(|bits| Self::from_bits(bits))
435
}
436
}
437
438
impl<S: Decode + ScalarBitSetStorage> Decode for ScalarBitSet<S> {
439
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
440
where
441
T: BytecodeStream,
442
{
443
S::decode(bytecode).map(ScalarBitSet::from)
444
}
445
}
446
447
impl<R: Reg + Decode> Decode for UpperRegSet<R> {
448
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
449
where
450
T: BytecodeStream,
451
{
452
ScalarBitSet::decode(bytecode).map(Self::from)
453
}
454
}
455
456
impl Decode for AddrO32 {
457
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
458
where
459
T: BytecodeStream,
460
{
461
Ok(AddrO32 {
462
addr: XReg::decode(bytecode)?,
463
offset: i32::decode(bytecode)?,
464
})
465
}
466
}
467
468
impl Decode for AddrZ {
469
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
470
where
471
T: BytecodeStream,
472
{
473
Ok(AddrZ {
474
addr: XReg::decode(bytecode)?,
475
offset: i32::decode(bytecode)?,
476
})
477
}
478
}
479
480
impl Decode for AddrG32 {
481
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
482
where
483
T: BytecodeStream,
484
{
485
Ok(AddrG32::from_bits(u32::decode(bytecode)?))
486
}
487
}
488
489
impl Decode for AddrG32Bne {
490
fn decode<T>(bytecode: &mut T) -> Result<Self, T::Error>
491
where
492
T: BytecodeStream,
493
{
494
Ok(AddrG32Bne::from_bits(u32::decode(bytecode)?))
495
}
496
}
497
498
/// A Pulley bytecode decoder.
499
///
500
/// Does not materialize bytecode instructions, instead all decoding methods are
501
/// given an `OpVisitor` implementation and the appropriate visitor methods are
502
/// called upon decoding an instruction. This minimizes the amount of times we
503
/// branch on the opcode, avoids constructing temporary storage, and plays well
504
/// with our variable-length instruction encoding.
505
#[derive(Default)]
506
pub struct Decoder {
507
_private: (),
508
}
509
510
impl Decoder {
511
/// Create a new decoder.
512
pub fn new() -> Self {
513
Self::default()
514
}
515
516
/// Decode all instructions in the visitor's bytecode stream.
517
///
518
/// The associated visitor method is invoked after each instruction is
519
/// decoded.
520
pub fn decode_all<'a, V>(visitor: &mut V) -> Result<Vec<V::Return>>
521
where
522
V: OpVisitor<BytecodeStream = SafeBytecodeStream<'a>> + ExtendedOpVisitor,
523
{
524
let mut decoder = Decoder::new();
525
let mut results = Vec::new();
526
527
while !visitor.bytecode().as_slice().is_empty() {
528
results.push(decoder.decode_one(visitor)?);
529
}
530
531
Ok(results)
532
}
533
}
534
535
/// An `OpVisitor` combinator to sequence one visitor and then another.
536
pub struct SequencedVisitor<'a, F, V1, V2> {
537
join: F,
538
v1: &'a mut V1,
539
v2: &'a mut V2,
540
}
541
542
impl<'a, F, V1, V2> SequencedVisitor<'a, F, V1, V2> {
543
/// Create a new sequenced visitor.
544
///
545
/// The given `join` function is used to combine the results of each
546
/// sub-visitor so that it can be returned from this combined visitor.
547
pub fn new(join: F, v1: &'a mut V1, v2: &'a mut V2) -> Self {
548
SequencedVisitor { join, v1, v2 }
549
}
550
}
551
552
macro_rules! define_decoder {
553
(
554
$(
555
$( #[$attr:meta] )*
556
$snake_name:ident = $name:ident $( {
557
$(
558
$( #[$field_attr:meta] )*
559
$field:ident : $field_ty:ty
560
),*
561
} )? ;
562
)*
563
) => {
564
impl Decoder {
565
/// Decode one instruction from the visitor's bytestream.
566
///
567
/// Upon decoding, the visitor's associated callback is invoked and
568
/// the results returned.
569
#[inline(always)]
570
pub fn decode_one<V>(
571
&mut self,
572
visitor: &mut V,
573
) -> Result<V::Return, <V::BytecodeStream as BytecodeStream>::Error>
574
where
575
V: OpVisitor + ExtendedOpVisitor,
576
{
577
visitor.before_visit();
578
579
let byte = u8::decode(visitor.bytecode())?;
580
let opcode = Opcode::new(byte).ok_or_else(|| {
581
visitor.bytecode().invalid_opcode(byte)
582
})?;
583
584
match opcode {
585
$(
586
Opcode::$name => {
587
$(
588
$(
589
let $field = <$field_ty>::decode(
590
visitor.bytecode(),
591
)?;
592
)*
593
)?
594
595
let ret = visitor.$snake_name($( $( $field ),* )?);
596
visitor.after_visit();
597
Ok(ret)
598
},
599
)*
600
Opcode::ExtendedOp => {
601
decode_one_extended(visitor)
602
}
603
}
604
}
605
}
606
607
/// Callbacks upon decoding instructions from bytecode.
608
///
609
/// Implement this trait for your type, give an instance of your type to
610
/// a `Decoder` method, and the `Decoder` will invoke the associated
611
/// method for each instruction that it decodes. For example, if the
612
/// `Decoder` decodes an `xadd32` instruction, then it will invoke the
613
/// `xadd32` visitor method, passing along any decoded immediates,
614
/// operands, etc... as arguments.
615
pub trait OpVisitor {
616
/// The type of this visitor's bytecode stream.
617
type BytecodeStream: BytecodeStream;
618
619
/// Get this visitor's underlying bytecode stream.
620
fn bytecode(&mut self) -> &mut Self::BytecodeStream;
621
622
/// The type of values returned by each visitor method.
623
type Return;
624
625
/// A callback invoked before starting to decode an instruction.
626
///
627
/// Does nothing by default.
628
fn before_visit(&mut self) {}
629
630
/// A callback invoked after an instruction has been completely
631
/// decoded.
632
///
633
/// Does nothing by default.
634
fn after_visit(&mut self) {}
635
636
$(
637
$( #[$attr] )*
638
fn $snake_name(&mut self $( $( , $field : $field_ty )* )? ) -> Self::Return;
639
)*
640
}
641
642
impl<F, T, V1, V2> OpVisitor for SequencedVisitor<'_, F, V1, V2>
643
where
644
F: FnMut(V1::Return, V2::Return) -> T,
645
V1: OpVisitor,
646
V2: OpVisitor<BytecodeStream = V1::BytecodeStream>,
647
{
648
type BytecodeStream = V1::BytecodeStream;
649
650
fn bytecode(&mut self) -> &mut Self::BytecodeStream {
651
self.v1.bytecode()
652
}
653
654
type Return = T;
655
656
fn before_visit(&mut self) {
657
self.v1.before_visit();
658
self.v2.before_visit();
659
}
660
661
fn after_visit(&mut self) {
662
*self.v2.bytecode() = *self.v1.bytecode();
663
self.v1.after_visit();
664
self.v2.after_visit();
665
}
666
667
$(
668
$( #[$attr] )*
669
fn $snake_name(&mut self $( $( , $field : $field_ty )* )? ) -> Self::Return {
670
let a = self.v1.$snake_name( $( $( $field , )* )? );
671
let b = self.v2.$snake_name( $( $( $field , )* )? );
672
(self.join)(a, b)
673
}
674
)*
675
}
676
};
677
}
678
for_each_op!(define_decoder);
679
680
macro_rules! define_extended_decoder {
681
(
682
$(
683
$( #[$attr:meta] )*
684
$snake_name:ident = $name:ident $( {
685
$(
686
$( #[$field_attr:meta] )*
687
$field:ident : $field_ty:ty
688
),*
689
} )? ;
690
)*
691
) => {
692
/// Like `OpVisitor` but for extended operations.
693
pub trait ExtendedOpVisitor: OpVisitor {
694
$(
695
$( #[$attr] )*
696
fn $snake_name(&mut self $( $( , $field : $field_ty )* )? ) -> Self::Return;
697
)*
698
}
699
700
fn decode_one_extended<V>(
701
visitor: &mut V,
702
) -> Result<V::Return, <V::BytecodeStream as BytecodeStream>::Error>
703
where
704
V: ExtendedOpVisitor,
705
{
706
let code = u16::decode(visitor.bytecode())?;
707
let opcode = ExtendedOpcode::new(code).ok_or_else(|| {
708
visitor.bytecode().invalid_extended_opcode(code)
709
})?;
710
711
match opcode {
712
$(
713
ExtendedOpcode::$name => {
714
$(
715
$(
716
let $field = <$field_ty>::decode(
717
visitor.bytecode(),
718
)?;
719
)*
720
)?
721
722
let ret = visitor.$snake_name($( $( $field ),* )?);
723
visitor.after_visit();
724
Ok(ret)
725
}
726
)*
727
}
728
}
729
730
731
impl<F, T, V1, V2> ExtendedOpVisitor for SequencedVisitor<'_, F, V1, V2>
732
where
733
F: FnMut(V1::Return, V2::Return) -> T,
734
V1: ExtendedOpVisitor,
735
V2: ExtendedOpVisitor<BytecodeStream = V1::BytecodeStream>,
736
{
737
$(
738
$( #[$attr] )*
739
fn $snake_name(&mut self $( $( , $field : $field_ty )* )? ) -> Self::Return {
740
let a = self.v1.$snake_name( $( $( $field , )* )? );
741
let b = self.v2.$snake_name( $( $( $field , )* )? );
742
(self.join)(a, b)
743
}
744
)*
745
}
746
};
747
}
748
for_each_extended_op!(define_extended_decoder);
749
750
/// Functions for decoding the operands of an instruction, assuming the opcode
751
/// has already been decoded.
752
pub mod operands {
753
use super::*;
754
755
macro_rules! define_operands_decoder {
756
(
757
$(
758
$( #[$attr:meta] )*
759
$snake_name:ident = $name:ident $( {
760
$(
761
$( #[$field_attr:meta] )*
762
$field:ident : $field_ty:ty
763
),*
764
} )? ;
765
)*
766
) => {
767
$(
768
#[allow(unused_variables, reason = "macro-generated")]
769
#[expect(missing_docs, reason = "macro-generated")]
770
pub fn $snake_name<T: BytecodeStream>(pc: &mut T) -> Result<($($($field_ty,)*)?), T::Error> {
771
Ok((($($((<$field_ty>::decode(pc))?,)*)?)))
772
}
773
)*
774
};
775
}
776
777
for_each_op!(define_operands_decoder);
778
779
/// Decode an extended opcode from `pc` to match the payload of the
780
/// "extended" opcode.
781
pub fn extended<T: BytecodeStream>(pc: &mut T) -> Result<(ExtendedOpcode,), T::Error> {
782
Ok((ExtendedOpcode::decode(pc)?,))
783
}
784
785
for_each_extended_op!(define_operands_decoder);
786
}
787
788