wasmi_ir/
primitive.rs

1use crate::{core::UntypedVal, immeditate::OutOfBoundsConst, Const16, Error};
2use core::marker::PhantomData;
3
4/// The sign of a value.
5#[derive(Debug)]
6pub struct Sign<T> {
7    /// Whether the sign value is positive.
8    is_positive: bool,
9    /// Required for the Rust compiler.
10    marker: PhantomData<fn() -> T>,
11}
12
13impl<T> Clone for Sign<T> {
14    fn clone(&self) -> Self {
15        *self
16    }
17}
18
19impl<T> Copy for Sign<T> {}
20
21impl<T> PartialEq for Sign<T> {
22    fn eq(&self, other: &Self) -> bool {
23        self.is_positive == other.is_positive
24    }
25}
26
27impl<T> Eq for Sign<T> {}
28
29impl<T> Sign<T> {
30    /// Create a new typed [`Sign`] with the given value.
31    fn new(is_positive: bool) -> Self {
32        Self {
33            is_positive,
34            marker: PhantomData,
35        }
36    }
37
38    /// Creates a new typed [`Sign`] that has positive polarity.
39    pub fn pos() -> Self {
40        Self::new(true)
41    }
42
43    /// Creates a new typed [`Sign`] that has negative polarity.
44    pub fn neg() -> Self {
45        Self::new(false)
46    }
47}
48
49macro_rules! impl_sign_for {
50    ( $($ty:ty),* $(,)? ) => {
51        $(
52            impl From<$ty> for Sign<$ty> {
53                fn from(value: $ty) -> Self {
54                    Self::new(value.is_sign_positive())
55                }
56            }
57
58            impl From<Sign<$ty>> for $ty {
59                fn from(sign: Sign<$ty>) -> Self {
60                    match sign.is_positive {
61                        true => 1.0,
62                        false => -1.0,
63                    }
64                }
65            }
66        )*
67    };
68}
69impl_sign_for!(f32, f64);
70
71/// A 16-bit signed offset for branch instructions.
72///
73/// This defines how much the instruction pointer is offset
74/// upon taking the respective branch.
75#[derive(Debug, Copy, Clone, PartialEq, Eq)]
76pub struct BranchOffset16(i16);
77
78impl From<i16> for BranchOffset16 {
79    fn from(offset: i16) -> Self {
80        Self(offset)
81    }
82}
83
84impl TryFrom<BranchOffset> for BranchOffset16 {
85    type Error = Error;
86
87    fn try_from(offset: BranchOffset) -> Result<Self, Self::Error> {
88        let Ok(offset16) = i16::try_from(offset.to_i32()) else {
89            return Err(Error::BranchOffsetOutOfBounds);
90        };
91        Ok(Self(offset16))
92    }
93}
94
95impl From<BranchOffset16> for BranchOffset {
96    fn from(offset: BranchOffset16) -> Self {
97        Self::from(i32::from(offset.to_i16()))
98    }
99}
100
101impl BranchOffset16 {
102    /// Returns `true` if the [`BranchOffset16`] has been initialized.
103    pub fn is_init(self) -> bool {
104        self.to_i16() != 0
105    }
106
107    /// Initializes the [`BranchOffset`] with a proper value.
108    ///
109    /// # Panics
110    ///
111    /// - If the [`BranchOffset`] have already been initialized.
112    /// - If the given [`BranchOffset`] is not properly initialized.
113    ///
114    /// # Errors
115    ///
116    /// If `valid_offset` cannot be encoded as 16-bit [`BranchOffset16`].
117    pub fn init(&mut self, valid_offset: BranchOffset) -> Result<(), Error> {
118        assert!(valid_offset.is_init());
119        assert!(!self.is_init());
120        let valid_offset16 = Self::try_from(valid_offset)?;
121        *self = valid_offset16;
122        Ok(())
123    }
124
125    /// Returns the `i16` representation of the [`BranchOffset`].
126    pub fn to_i16(self) -> i16 {
127        self.0
128    }
129}
130
131/// A signed offset for branch instructions.
132///
133/// This defines how much the instruction pointer is offset
134/// upon taking the respective branch.
135#[derive(Debug, Copy, Clone, PartialEq, Eq)]
136pub struct BranchOffset(i32);
137
138impl From<i32> for BranchOffset {
139    fn from(index: i32) -> Self {
140        Self(index)
141    }
142}
143
144impl BranchOffset {
145    /// Creates an uninitialized [`BranchOffset`].
146    pub fn uninit() -> Self {
147        Self(0)
148    }
149
150    /// Creates an initialized [`BranchOffset`] from `src` to `dst`.
151    ///
152    /// # Errors
153    ///
154    /// If the resulting [`BranchOffset`] is out of bounds.
155    pub fn from_src_to_dst(src: u32, dst: u32) -> Result<Self, Error> {
156        let src = i64::from(src);
157        let dst = i64::from(dst);
158        let Some(offset) = dst.checked_sub(src) else {
159            // Note: This never needs to be called on backwards branches since they are immediated resolved.
160            unreachable!(
161                "offset for forward branches must have `src` be smaller than or equal to `dst`"
162            );
163        };
164        let Ok(offset) = i32::try_from(offset) else {
165            return Err(Error::BranchOffsetOutOfBounds);
166        };
167        Ok(Self(offset))
168    }
169
170    /// Returns `true` if the [`BranchOffset`] has been initialized.
171    pub fn is_init(self) -> bool {
172        self.to_i32() != 0
173    }
174
175    /// Initializes the [`BranchOffset`] with a proper value.
176    ///
177    /// # Panics
178    ///
179    /// - If the [`BranchOffset`] have already been initialized.
180    /// - If the given [`BranchOffset`] is not properly initialized.
181    pub fn init(&mut self, valid_offset: BranchOffset) {
182        assert!(valid_offset.is_init());
183        assert!(!self.is_init());
184        *self = valid_offset;
185    }
186
187    /// Returns the `i32` representation of the [`BranchOffset`].
188    pub fn to_i32(self) -> i32 {
189        self.0
190    }
191}
192
193/// The accumulated fuel to execute a block via [`Instruction::ConsumeFuel`].
194///
195/// [`Instruction::ConsumeFuel`]: [`super::Instruction::ConsumeFuel`]
196#[derive(Debug, Copy, Clone, PartialEq, Eq)]
197#[repr(transparent)]
198pub struct BlockFuel(u32);
199
200impl From<u32> for BlockFuel {
201    fn from(value: u32) -> Self {
202        Self(value)
203    }
204}
205
206impl TryFrom<u64> for BlockFuel {
207    type Error = Error;
208
209    fn try_from(index: u64) -> Result<Self, Self::Error> {
210        match u32::try_from(index) {
211            Ok(index) => Ok(Self(index)),
212            Err(_) => Err(Error::BlockFuelOutOfBounds),
213        }
214    }
215}
216
217impl BlockFuel {
218    /// Bump the fuel by `amount` if possible.
219    ///
220    /// # Errors
221    ///
222    /// If the new fuel amount after this operation is out of bounds.
223    pub fn bump_by(&mut self, amount: u64) -> Result<(), Error> {
224        let new_amount = self
225            .to_u64()
226            .checked_add(amount)
227            .ok_or(Error::BlockFuelOutOfBounds)?;
228        self.0 = u32::try_from(new_amount).map_err(|_| Error::BlockFuelOutOfBounds)?;
229        Ok(())
230    }
231
232    /// Returns the index value as `u64`.
233    pub fn to_u64(self) -> u64 {
234        u64::from(self.0)
235    }
236}
237
238macro_rules! for_each_comparator {
239    ($mac:ident) => {
240        $mac! {
241            I32Eq,
242            I32Ne,
243            I32LtS,
244            I32LtU,
245            I32LeS,
246            I32LeU,
247
248            I32And,
249            I32Or,
250            I32Xor,
251            I32Nand,
252            I32Nor,
253            I32Xnor,
254
255            I64Eq,
256            I64Ne,
257            I64LtS,
258            I64LtU,
259            I64LeS,
260            I64LeU,
261
262            I64And,
263            I64Or,
264            I64Xor,
265            I64Nand,
266            I64Nor,
267            I64Xnor,
268
269            F32Eq,
270            F32Ne,
271            F32Lt,
272            F32Le,
273            F32NotLt,
274            F32NotLe,
275
276            F64Eq,
277            F64Ne,
278            F64Lt,
279            F64Le,
280            F64NotLt,
281            F64NotLe,
282        }
283    };
284}
285
286macro_rules! define_comparator {
287    ( $( $name:ident ),* $(,)? ) => {
288        /// Encodes the conditional branch comparator.
289        #[derive(Debug, Copy, Clone, PartialEq, Eq)]
290        #[repr(u32)]
291        pub enum Comparator {
292            $( $name ),*
293        }
294
295        impl TryFrom<u32> for Comparator {
296            type Error = Error;
297
298            fn try_from(value: u32) -> Result<Self, Self::Error> {
299                match value {
300                    $(
301                        x if x == Self::$name as u32 => Ok(Self::$name),
302                    )*
303                    _ => Err(Error::ComparatorOutOfBounds),
304                }
305            }
306        }
307
308        impl From<Comparator> for u32 {
309            fn from(cmp: Comparator) -> u32 {
310                cmp as u32
311            }
312        }
313    };
314}
315for_each_comparator!(define_comparator);
316
317/// Special parameter for [`Instruction::BranchCmpFallback`].
318///
319/// # Note
320///
321/// This type can be converted from and to a `u64` or [`UntypedVal`] value.
322///
323/// [`Instruction::BranchCmpFallback`]: crate::Instruction::BranchCmpFallback
324#[derive(Debug, Copy, Clone, PartialEq, Eq)]
325pub struct ComparatorAndOffset {
326    /// Encodes the actual binary operator for the conditional branch.
327    pub cmp: Comparator,
328    //// Encodes the 32-bit branching offset.
329    pub offset: BranchOffset,
330}
331
332impl ComparatorAndOffset {
333    /// Create a new [`ComparatorAndOffset`].
334    pub fn new(cmp: Comparator, offset: BranchOffset) -> Self {
335        Self { cmp, offset }
336    }
337
338    /// Creates a new [`ComparatorAndOffset`] from the given `u64` value.
339    ///
340    /// Returns `None` if the `u64` has an invalid encoding.
341    pub fn from_u64(value: u64) -> Option<Self> {
342        let hi = (value >> 32) as u32;
343        let lo = (value & 0xFFFF_FFFF) as u32;
344        let cmp = Comparator::try_from(hi).ok()?;
345        let offset = BranchOffset::from(lo as i32);
346        Some(Self { cmp, offset })
347    }
348
349    /// Converts the [`ComparatorAndOffset`] into an `u64` value.
350    pub fn as_u64(&self) -> u64 {
351        let hi = self.cmp as u64;
352        let lo = self.offset.to_i32() as u64;
353        (hi << 32) | lo
354    }
355}
356
357impl From<ComparatorAndOffset> for UntypedVal {
358    fn from(params: ComparatorAndOffset) -> Self {
359        Self::from(params.as_u64())
360    }
361}
362
363/// A typed shift amount for shift and rotate instructions.
364#[derive(Debug, Copy, Clone, PartialEq, Eq)]
365pub struct ShiftAmount<T> {
366    /// The underlying wrapped shift amount.
367    value: Const16<T>,
368}
369
370macro_rules! impl_from_shift_amount_for {
371    ( $($ty:ty),* $(,)? ) => {
372        $(
373            impl From<ShiftAmount<$ty>> for $ty {
374                fn from(shamt: ShiftAmount<$ty>) -> $ty {
375                    shamt.value.into()
376                }
377            }
378        )*
379    };
380}
381impl_from_shift_amount_for!(i32, i64, u32);
382
383/// Integer types that can be used as shift amount in shift or rotate instructions.
384pub trait IntoShiftAmount: Sized {
385    type Output;
386    type Input;
387
388    /// Converts `self` into a [`ShiftAmount`] if possible.
389    fn into_shift_amount(input: Self::Input) -> Option<Self::Output>;
390}
391
392macro_rules! impl_shift_amount {
393    ( $( ($ty:ty, $bits:literal, $ty16:ty, $shamt:ty) ),* $(,)? ) => {
394        $(
395            impl IntoShiftAmount for $ty {
396                type Output = ShiftAmount<$shamt>;
397                type Input = $shamt;
398
399                fn into_shift_amount(input: Self::Input) -> Option<Self::Output> {
400                    let value = (input % $bits) as $ty16;
401                    if value == 0 {
402                        return None
403                    }
404                    Some(ShiftAmount { value: Const16::from(value) })
405                }
406            }
407        )*
408    };
409}
410impl_shift_amount! {
411    // used by scalar types such as `i32` and `i64`
412    (i32, 32, i16, i32),
413    (i64, 64, i16, i64),
414
415    // used by SIMD types such as `i8x16`, `i16x8`, `i32x4` and `i64x2`
416    (u8 ,  8, u16, u32),
417    (u16, 16, u16, u32),
418    (u32, 32, u16, u32),
419    (u64, 64, u16, u32),
420}
421
422/// A 64-bit offset in Wasmi bytecode.
423#[derive(Debug, Copy, Clone, PartialEq, Eq)]
424#[repr(transparent)]
425pub struct Offset64(u64);
426
427/// The high 32 bits of an [`Offset64`].
428#[derive(Debug, Copy, Clone, PartialEq, Eq)]
429#[repr(transparent)]
430pub struct Offset64Hi(pub(crate) u32);
431
432/// The low 32 bits of an [`Offset64`].
433#[derive(Debug, Copy, Clone, PartialEq, Eq)]
434#[repr(transparent)]
435pub struct Offset64Lo(pub(crate) u32);
436
437impl Offset64 {
438    /// Creates a new [`Offset64`] lo-hi pair from the given `offset`.
439    pub fn split(offset: u64) -> (Offset64Hi, Offset64Lo) {
440        let offset_lo = (offset & 0xFFFF_FFFF) as u32;
441        let offset_hi = (offset >> 32) as u32;
442        (Offset64Hi(offset_hi), Offset64Lo(offset_lo))
443    }
444
445    /// Combines the given [`Offset64`] lo-hi pair into an [`Offset64`].
446    pub fn combine(hi: Offset64Hi, lo: Offset64Lo) -> Self {
447        let hi = hi.0 as u64;
448        let lo = lo.0 as u64;
449        Self((hi << 32) | lo)
450    }
451}
452
453#[test]
454fn test_offset64_split_combine() {
455    let test_values = [
456        0,
457        1,
458        1 << 1,
459        u64::MAX,
460        u64::MAX - 1,
461        42,
462        77,
463        u64::MAX >> 1,
464        0xFFFF_FFFF_0000_0000,
465        0x0000_0000_FFFF_FFFF,
466        0xF0F0_F0F0_0F0F_0F0F,
467    ];
468    for value in test_values {
469        let (hi, lo) = Offset64::split(value);
470        let combined = u64::from(Offset64::combine(hi, lo));
471        assert_eq!(combined, value);
472    }
473}
474
475impl From<u64> for Offset64 {
476    fn from(offset: u64) -> Self {
477        Self(offset)
478    }
479}
480
481impl From<Offset64> for u64 {
482    fn from(offset: Offset64) -> Self {
483        offset.0
484    }
485}
486
487/// An 8-bit encoded load or store address offset.
488#[derive(Debug, Copy, Clone, PartialEq, Eq)]
489#[repr(transparent)]
490pub struct Offset8(u8);
491
492impl TryFrom<u64> for Offset8 {
493    type Error = OutOfBoundsConst;
494
495    fn try_from(address: u64) -> Result<Self, Self::Error> {
496        u8::try_from(address)
497            .map(Self)
498            .map_err(|_| OutOfBoundsConst)
499    }
500}
501
502impl From<Offset8> for Offset64 {
503    fn from(offset: Offset8) -> Self {
504        Offset64(u64::from(offset.0))
505    }
506}
507
508/// A 16-bit encoded load or store address offset.
509#[derive(Debug, Copy, Clone, PartialEq, Eq)]
510#[repr(transparent)]
511pub struct Offset16(Const16<u64>);
512
513impl TryFrom<u64> for Offset16 {
514    type Error = OutOfBoundsConst;
515
516    fn try_from(address: u64) -> Result<Self, Self::Error> {
517        <Const16<u64>>::try_from(address).map(Self)
518    }
519}
520
521impl From<Offset16> for Offset64 {
522    fn from(offset: Offset16) -> Self {
523        Offset64(u64::from(offset.0))
524    }
525}
526
527/// A 64-bit memory address used for some load and store instructions.
528#[derive(Debug, Copy, Clone, PartialEq, Eq)]
529#[repr(transparent)]
530pub struct Address(u64);
531
532impl TryFrom<u64> for Address {
533    type Error = OutOfBoundsConst;
534
535    fn try_from(address: u64) -> Result<Self, OutOfBoundsConst> {
536        if usize::try_from(address).is_err() {
537            return Err(OutOfBoundsConst);
538        };
539        Ok(Self(address))
540    }
541}
542
543impl From<Address> for usize {
544    fn from(address: Address) -> Self {
545        // Note: no checks are needed since we statically ensured that
546        // `Address32` can be safely and losslessly cast to `usize`.
547        debug_assert!(usize::try_from(address.0).is_ok());
548        address.0 as usize
549    }
550}
551
552impl From<Address> for u64 {
553    fn from(address: Address) -> Self {
554        address.0
555    }
556}
557
558/// A 32-bit memory address used for some load and store instructions.
559#[derive(Debug, Copy, Clone, PartialEq, Eq)]
560#[repr(transparent)]
561pub struct Address32(u32);
562
563impl TryFrom<Address> for Address32 {
564    type Error = OutOfBoundsConst;
565
566    fn try_from(address: Address) -> Result<Self, OutOfBoundsConst> {
567        let Ok(address) = u32::try_from(u64::from(address)) else {
568            return Err(OutOfBoundsConst);
569        };
570        Ok(Self(address))
571    }
572}
573
574impl From<Address32> for usize {
575    fn from(address: Address32) -> Self {
576        // Note: no checks are needed since we statically ensured that
577        // `Address32` can be safely and losslessly cast to `usize`.
578        debug_assert!(usize::try_from(address.0).is_ok());
579        address.0 as usize
580    }
581}