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            I32AndEqz,
252            I32OrEqz,
253            I32XorEqz,
254
255            I64Eq,
256            I64Ne,
257            I64LtS,
258            I64LtU,
259            I64LeS,
260            I64LeU,
261
262            F32Eq,
263            F32Ne,
264            F32Lt,
265            F32Le,
266
267            F64Eq,
268            F64Ne,
269            F64Lt,
270            F64Le,
271        }
272    };
273}
274
275macro_rules! define_comparator {
276    ( $( $name:ident ),* $(,)? ) => {
277        /// Encodes the conditional branch comparator.
278        #[derive(Debug, Copy, Clone, PartialEq, Eq)]
279        #[repr(u32)]
280        pub enum Comparator {
281            $( $name ),*
282        }
283
284        impl TryFrom<u32> for Comparator {
285            type Error = Error;
286
287            fn try_from(value: u32) -> Result<Self, Self::Error> {
288                match value {
289                    $(
290                        x if x == Self::$name as u32 => Ok(Self::$name),
291                    )*
292                    _ => Err(Error::ComparatorOutOfBounds),
293                }
294            }
295        }
296
297        impl From<Comparator> for u32 {
298            fn from(cmp: Comparator) -> u32 {
299                cmp as u32
300            }
301        }
302    };
303}
304for_each_comparator!(define_comparator);
305
306/// Special parameter for [`Instruction::BranchCmpFallback`].
307///
308/// # Note
309///
310/// This type can be converted from and to a `u64` or [`UntypedVal`] value.
311///
312/// [`Instruction::BranchCmpFallback`]: crate::Instruction::BranchCmpFallback
313#[derive(Debug, Copy, Clone, PartialEq, Eq)]
314pub struct ComparatorAndOffset {
315    /// Encodes the actual binary operator for the conditional branch.
316    pub cmp: Comparator,
317    //// Encodes the 32-bit branching offset.
318    pub offset: BranchOffset,
319}
320
321impl ComparatorAndOffset {
322    /// Create a new [`ComparatorAndOffset`].
323    pub fn new(cmp: Comparator, offset: BranchOffset) -> Self {
324        Self { cmp, offset }
325    }
326
327    /// Creates a new [`ComparatorAndOffset`] from the given `u64` value.
328    ///
329    /// Returns `None` if the `u64` has an invalid encoding.
330    pub fn from_u64(value: u64) -> Option<Self> {
331        let hi = (value >> 32) as u32;
332        let lo = (value & 0xFFFF_FFFF) as u32;
333        let cmp = Comparator::try_from(hi).ok()?;
334        let offset = BranchOffset::from(lo as i32);
335        Some(Self { cmp, offset })
336    }
337
338    /// Creates a new [`ComparatorAndOffset`] from the given [`UntypedVal`].
339    ///
340    /// Returns `None` if the [`UntypedVal`] has an invalid encoding.
341    pub fn from_untyped(value: UntypedVal) -> Option<Self> {
342        Self::from_u64(u64::from(value))
343    }
344
345    /// Converts the [`ComparatorAndOffset`] into an `u64` value.
346    pub fn as_u64(&self) -> u64 {
347        let hi = self.cmp as u64;
348        let lo = self.offset.to_i32() as u64;
349        (hi << 32) | lo
350    }
351}
352
353impl From<ComparatorAndOffset> for UntypedVal {
354    fn from(params: ComparatorAndOffset) -> Self {
355        Self::from(params.as_u64())
356    }
357}
358
359/// A typed shift amount for shift and rotate instructions.
360#[derive(Debug, Copy, Clone, PartialEq, Eq)]
361pub struct ShiftAmount<T> {
362    /// The underlying wrapped shift amount.
363    value: Const16<T>,
364}
365
366/// Integer types that can be used as shift amount in shift or rotate instructions.
367pub trait IntoShiftAmount: Sized {
368    /// Converts `self` into a [`ShiftAmount`] if possible.
369    fn into_shift_amount(self) -> Option<ShiftAmount<Self>>;
370}
371
372macro_rules! impl_shift_amount {
373    ( $( ($ty:ty, $bits:literal) ),* $(,)? ) => {
374        $(
375            impl IntoShiftAmount for $ty {
376                fn into_shift_amount(self) -> Option<ShiftAmount<Self>> {
377                    <ShiftAmount<$ty>>::new(self)
378                }
379            }
380
381            impl ShiftAmount<$ty> {
382                /// Creates a new [`ShiftAmount`] for the given `value`.
383                ///
384                /// Returns `None` if `value` causes a no-op shift.
385                pub fn new(value: $ty) -> Option<Self> {
386                    let value = (value % $bits) as i16;
387                    if value == 0 {
388                        return None
389                    }
390                    Some(Self { value: Const16::from(value) })
391                }
392            }
393
394            impl From<ShiftAmount<$ty>> for $ty {
395                fn from(shamt: ShiftAmount<$ty>) -> Self {
396                    shamt.value.into()
397                }
398            }
399        )*
400    };
401}
402impl_shift_amount! {
403    (i32, 32),
404    (i64, 64),
405}
406
407/// A 64-bit offset in Wasmi bytecode.
408#[derive(Debug, Copy, Clone, PartialEq, Eq)]
409#[repr(transparent)]
410pub struct Offset64(u64);
411
412/// The high 32 bits of an [`Offset64`].
413#[derive(Debug, Copy, Clone, PartialEq, Eq)]
414#[repr(transparent)]
415pub struct Offset64Hi(pub(crate) u32);
416
417/// The low 32 bits of an [`Offset64`].
418#[derive(Debug, Copy, Clone, PartialEq, Eq)]
419#[repr(transparent)]
420pub struct Offset64Lo(pub(crate) u32);
421
422impl Offset64 {
423    /// Creates a new [`Offset64`] lo-hi pair from the given `offset`.
424    pub fn split(offset: u64) -> (Offset64Hi, Offset64Lo) {
425        let offset_lo = (offset & 0xFFFF_FFFF) as u32;
426        let offset_hi = (offset >> 32) as u32;
427        (Offset64Hi(offset_hi), Offset64Lo(offset_lo))
428    }
429
430    /// Combines the given [`Offset64`] lo-hi pair into an [`Offset64`].
431    pub fn combine(hi: Offset64Hi, lo: Offset64Lo) -> Self {
432        let hi = hi.0 as u64;
433        let lo = lo.0 as u64;
434        Self((hi << 32) | lo)
435    }
436}
437
438#[test]
439fn test_offset64_split_combine() {
440    let test_values = [
441        0,
442        1,
443        1 << 1,
444        u64::MAX,
445        u64::MAX - 1,
446        42,
447        77,
448        u64::MAX >> 1,
449        0xFFFF_FFFF_0000_0000,
450        0x0000_0000_FFFF_FFFF,
451        0xF0F0_F0F0_0F0F_0F0F,
452    ];
453    for value in test_values {
454        let (hi, lo) = Offset64::split(value);
455        let combined = u64::from(Offset64::combine(hi, lo));
456        assert_eq!(combined, value);
457    }
458}
459
460impl From<u64> for Offset64 {
461    fn from(offset: u64) -> Self {
462        Self(offset)
463    }
464}
465
466impl From<Offset64> for u64 {
467    fn from(offset: Offset64) -> Self {
468        offset.0
469    }
470}
471
472/// A 16-bit encoded load or store address offset.
473#[derive(Debug, Copy, Clone, PartialEq, Eq)]
474#[repr(transparent)]
475pub struct Offset16(Const16<u64>);
476
477impl TryFrom<u64> for Offset16 {
478    type Error = OutOfBoundsConst;
479
480    fn try_from(address: u64) -> Result<Self, Self::Error> {
481        <Const16<u64>>::try_from(address).map(Self)
482    }
483}
484
485impl From<Offset16> for Offset64 {
486    fn from(offset: Offset16) -> Self {
487        Offset64(u64::from(offset.0))
488    }
489}
490
491/// A 64-bit memory address used for some load and store instructions.
492#[derive(Debug, Copy, Clone, PartialEq, Eq)]
493#[repr(transparent)]
494pub struct Address(u64);
495
496impl TryFrom<u64> for Address {
497    type Error = OutOfBoundsConst;
498
499    fn try_from(address: u64) -> Result<Self, OutOfBoundsConst> {
500        if usize::try_from(address).is_err() {
501            return Err(OutOfBoundsConst);
502        };
503        Ok(Self(address))
504    }
505}
506
507impl From<Address> for usize {
508    fn from(address: Address) -> Self {
509        // Note: no checks are needed since we statically ensured that
510        // `Address32` can be safely and losslessly cast to `usize`.
511        debug_assert!(usize::try_from(address.0).is_ok());
512        address.0 as usize
513    }
514}
515
516impl From<Address> for u64 {
517    fn from(address: Address) -> Self {
518        address.0
519    }
520}
521
522/// A 32-bit memory address used for some load and store instructions.
523#[derive(Debug, Copy, Clone, PartialEq, Eq)]
524#[repr(transparent)]
525pub struct Address32(u32);
526
527impl TryFrom<Address> for Address32 {
528    type Error = OutOfBoundsConst;
529
530    fn try_from(address: Address) -> Result<Self, OutOfBoundsConst> {
531        let Ok(address) = u32::try_from(u64::from(address)) else {
532            return Err(OutOfBoundsConst);
533        };
534        Ok(Self(address))
535    }
536}
537
538impl From<Address32> for usize {
539    fn from(address: Address32) -> Self {
540        // Note: no checks are needed since we statically ensured that
541        // `Address32` can be safely and losslessly cast to `usize`.
542        debug_assert!(usize::try_from(address.0).is_ok());
543        address.0 as usize
544    }
545}