Skip to main content

bytecode_lang/
policy.rs

1//! Operation policies (`specs/OPS.md` §2) and the packed modifier bytes that
2//! carry them on each instruction.
3//!
4//! OPS fixes a policy per op instance at lowering time, so that every tier
5//! sees the same one. LSB therefore stores the policy *in the instruction*:
6//! an `iadd` that wraps and an `iadd` that raises on overflow are different
7//! instructions, and no tier ever consults a language-wide setting at run
8//! time. Each packed type fits the single modifier byte of an eight-byte
9//! instruction and rejects bit patterns that name no policy, so a decoded
10//! instruction always carries a meaningful one.
11
12use core::fmt;
13
14code_enum! {
15    /// An integer type: the width and signedness an integer instruction
16    /// works at (OPS §1).
17    ///
18    /// # Examples
19    ///
20    /// ```
21    /// use bytecode_lang::IntTy;
22    ///
23    /// assert_eq!(IntTy::I64.bits(), 64);
24    /// assert!(IntTy::I8.is_signed());
25    /// assert!(!IntTy::U32.is_signed());
26    /// assert_eq!(IntTy::from_code(3), Some(IntTy::I64));
27    /// assert_eq!(IntTy::U16.to_string(), "u16");
28    /// ```
29    IntTy {
30        /// Signed 8-bit.
31        I8 = 0 => "i8",
32        /// Signed 16-bit.
33        I16 = 1 => "i16",
34        /// Signed 32-bit.
35        I32 = 2 => "i32",
36        /// Signed 64-bit.
37        I64 = 3 => "i64",
38        /// Unsigned 8-bit.
39        U8 = 4 => "u8",
40        /// Unsigned 16-bit.
41        U16 = 5 => "u16",
42        /// Unsigned 32-bit.
43        U32 = 6 => "u32",
44        /// Unsigned 64-bit.
45        U64 = 7 => "u64",
46    }
47}
48
49impl IntTy {
50    /// The width in bits: 8, 16, 32, or 64.
51    ///
52    /// # Examples
53    ///
54    /// ```
55    /// use bytecode_lang::IntTy;
56    ///
57    /// assert_eq!(IntTy::U16.bits(), 16);
58    /// ```
59    #[must_use]
60    pub const fn bits(self) -> u32 {
61        match self {
62            IntTy::I8 | IntTy::U8 => 8,
63            IntTy::I16 | IntTy::U16 => 16,
64            IntTy::I32 | IntTy::U32 => 32,
65            IntTy::I64 | IntTy::U64 => 64,
66        }
67    }
68
69    /// Whether the type is two's-complement signed.
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// use bytecode_lang::IntTy;
75    ///
76    /// assert!(IntTy::I32.is_signed());
77    /// assert!(!IntTy::U8.is_signed());
78    /// ```
79    #[must_use]
80    pub const fn is_signed(self) -> bool {
81        (self as u8) < 4
82    }
83
84    /// The type named by the low three bits of `bits`. Total: every 3-bit
85    /// value names a type.
86    pub(crate) const fn from_low3(bits: u8) -> IntTy {
87        match bits & 7 {
88            0 => IntTy::I8,
89            1 => IntTy::I16,
90            2 => IntTy::I32,
91            3 => IntTy::I64,
92            4 => IntTy::U8,
93            5 => IntTy::U16,
94            6 => IntTy::U32,
95            _ => IntTy::U64,
96        }
97    }
98}
99
100code_enum! {
101    /// A floating-point type (IEEE 754 binary32 or binary64).
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// use bytecode_lang::FloatTy;
107    ///
108    /// assert_eq!(FloatTy::F64.to_string(), "f64");
109    /// assert_eq!(FloatTy::from_code(0), Some(FloatTy::F32));
110    /// ```
111    FloatTy {
112        /// IEEE 754 binary32.
113        F32 = 0 => "f32",
114        /// IEEE 754 binary64.
115        F64 = 1 => "f64",
116    }
117}
118
119code_enum! {
120    /// The `overflow` policy: what integer add, sub, mul, neg, abs, signed
121    /// `MIN / -1`, and `int_cast` do when the exact result does not fit.
122    ///
123    /// [`Promote`](Overflow::Promote) exists for dynamic-value operations only
124    /// (OPS §2, added for Mox/PHP): it is valid only where the result
125    /// register is `dyn`. [`ModuleBuilder`](crate::ModuleBuilder) refuses it
126    /// on a statically typed destination it can see, and the verifier
127    /// (v0.5) refuses it everywhere else.
128    ///
129    /// # Examples
130    ///
131    /// ```
132    /// use bytecode_lang::Overflow;
133    ///
134    /// assert_eq!(Overflow::default(), Overflow::Error);
135    /// assert_eq!(Overflow::Wrap.to_string(), "wrap");
136    /// assert_eq!(Overflow::from_code(3), Some(Overflow::Promote));
137    /// ```
138    #[derive(Default)]
139    Overflow {
140        /// Raise `ArithOverflow` (E0001) through the language's error model.
141        #[default]
142        Error = 0 => "error",
143        /// Two's-complement wrap-around; never an error.
144        Wrap = 1 => "wrap",
145        /// Abort the program with `ArithOverflow` (E0001); not catchable.
146        Trap = 2 => "trap",
147        /// The result is the `f64` nearest the exact mathematical result
148        /// (ties to even), as PHP does for `PHP_INT_MAX + 1`. Dynamic
149        /// results only.
150        Promote = 3 => "promote",
151    }
152}
153
154code_enum! {
155    /// The `div_zero` policy: what integer div, rem, floor_div, and
156    /// floor_mod do when the divisor is zero.
157    ///
158    /// # Examples
159    ///
160    /// ```
161    /// use bytecode_lang::DivZero;
162    ///
163    /// assert_eq!(DivZero::default(), DivZero::Error);
164    /// ```
165    #[derive(Default)]
166    DivZero {
167        /// Raise `DivByZero` (E0002) through the language's error model.
168        #[default]
169        Error = 0 => "error",
170        /// Abort the program with `DivByZero` (E0002); not catchable.
171        Trap = 1 => "trap",
172    }
173}
174
175code_enum! {
176    /// The `shift` policy: what shl and shr do when the amount is at least
177    /// the bit width, or negative for a signed amount.
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// use bytecode_lang::Shift;
183    ///
184    /// assert_eq!(Shift::default(), Shift::Error);
185    /// assert_eq!(Shift::from_code(2), Some(Shift::Saturate));
186    /// assert_eq!(Shift::from_code(3), None);
187    /// ```
188    #[derive(Default)]
189    Shift {
190        /// Raise `ShiftOutOfRange` (E0003) through the language's error model.
191        #[default]
192        Error = 0 => "error",
193        /// Use the amount modulo the width (`n & (width - 1)`); never an error.
194        Mask = 1 => "mask",
195        /// PHP's rule (OPS v2): an amount at or above the width gives `0`
196        /// (`shr` of a negative signed value gives `-1`); a **negative**
197        /// amount is still `ShiftOutOfRange` (E0003), never masked.
198        Saturate = 2 => "saturate",
199    }
200}
201
202code_enum! {
203    /// The `float_to_int` policy: what float-to-integer conversion does with
204    /// NaN or an out-of-range value.
205    ///
206    /// # Examples
207    ///
208    /// ```
209    /// use bytecode_lang::FloatToInt;
210    ///
211    /// assert_eq!(FloatToInt::default(), FloatToInt::Error);
212    /// ```
213    #[derive(Default)]
214    FloatToInt {
215        /// Raise `InvalidConversion` (E0004) through the language's error model.
216        #[default]
217        Error = 0 => "error",
218        /// Clamp to the type's MIN/MAX; NaN becomes 0.
219        Saturate = 1 => "saturate",
220    }
221}
222
223/// The complete OPS policy set, packed into six bits: the modifier byte of
224/// every dynamic arithmetic instruction (`dadd`, `dshl`, `dpow`, ...).
225///
226/// Layout: bits 0–1 `overflow`, bit 2 `div_zero`, bits 3–4 `shift` (the
227/// value 3 is reserved), bit 5 `float_to_int`; bits 6–7 are zero. An
228/// instruction carries the whole set even when only part of it can apply (a
229/// `dadd` never shifts), so a code generator stamps one policy per language
230/// onto every instruction and the semantics pick the part they need. The
231/// default is all-`error`, the OPS default. (Typed integer instructions carry
232/// an [`IntOp`], which has room for every part but `float_to_int`; the
233/// float-to-integer conversions carry a [`FloatConv`].)
234///
235/// # Examples
236///
237/// ```
238/// use bytecode_lang::{Overflow, Policy, Shift};
239///
240/// let p = Policy::new().with_overflow(Overflow::Wrap).with_shift(Shift::Mask);
241/// assert_eq!(p.overflow(), Overflow::Wrap);
242/// assert_eq!(p.shift(), Shift::Mask);
243/// assert_eq!(p.to_string(), "wrap.mask");
244/// assert_eq!(Policy::from_bits(p.bits()), Some(p));
245/// assert_eq!(Policy::from_bits(0b11).map(|p| p.overflow()), Some(Overflow::Promote));
246/// assert_eq!(Policy::from_bits(0b1_1000), None); // shift code 3 is reserved
247/// assert_eq!(Policy::from_bits(0b100_0000), None); // bit 6 is reserved
248/// ```
249#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
250pub struct Policy(u8);
251
252impl Policy {
253    /// The OPS defaults: every policy is `error`.
254    pub const DEFAULT: Policy = Policy(0);
255
256    /// The OPS defaults: every policy is `error`.
257    ///
258    /// # Examples
259    ///
260    /// ```
261    /// use bytecode_lang::{Overflow, Policy};
262    ///
263    /// assert_eq!(Policy::new().overflow(), Overflow::Error);
264    /// ```
265    #[must_use]
266    pub const fn new() -> Self {
267        Policy(0)
268    }
269
270    /// The `overflow` policy.
271    ///
272    /// # Examples
273    ///
274    /// ```
275    /// use bytecode_lang::{Overflow, Policy};
276    ///
277    /// assert_eq!(Policy::new().with_overflow(Overflow::Trap).overflow(), Overflow::Trap);
278    /// ```
279    #[must_use]
280    pub const fn overflow(self) -> Overflow {
281        match self.0 & 3 {
282            0 => Overflow::Error,
283            1 => Overflow::Wrap,
284            2 => Overflow::Trap,
285            _ => Overflow::Promote,
286        }
287    }
288
289    /// The `div_zero` policy.
290    ///
291    /// # Examples
292    ///
293    /// ```
294    /// use bytecode_lang::{DivZero, Policy};
295    ///
296    /// assert_eq!(Policy::new().with_div_zero(DivZero::Trap).div_zero(), DivZero::Trap);
297    /// ```
298    #[must_use]
299    pub const fn div_zero(self) -> DivZero {
300        if self.0 & 4 == 0 {
301            DivZero::Error
302        } else {
303            DivZero::Trap
304        }
305    }
306
307    /// The `shift` policy.
308    ///
309    /// # Examples
310    ///
311    /// ```
312    /// use bytecode_lang::{Policy, Shift};
313    ///
314    /// assert_eq!(Policy::new().with_shift(Shift::Mask).shift(), Shift::Mask);
315    /// assert_eq!(Policy::new().with_shift(Shift::Saturate).shift(), Shift::Saturate);
316    /// ```
317    #[must_use]
318    pub const fn shift(self) -> Shift {
319        match (self.0 >> 3) & 3 {
320            0 => Shift::Error,
321            1 => Shift::Mask,
322            // 3 never survives `from_bits` or `with_shift`.
323            _ => Shift::Saturate,
324        }
325    }
326
327    /// The `float_to_int` policy.
328    ///
329    /// # Examples
330    ///
331    /// ```
332    /// use bytecode_lang::{FloatToInt, Policy};
333    ///
334    /// let p = Policy::new().with_float_to_int(FloatToInt::Saturate);
335    /// assert_eq!(p.float_to_int(), FloatToInt::Saturate);
336    /// ```
337    #[must_use]
338    pub const fn float_to_int(self) -> FloatToInt {
339        if self.0 & 32 == 0 {
340            FloatToInt::Error
341        } else {
342            FloatToInt::Saturate
343        }
344    }
345
346    /// This policy with `overflow` replaced.
347    ///
348    /// # Examples
349    ///
350    /// ```
351    /// use bytecode_lang::{Overflow, Policy};
352    ///
353    /// assert_eq!(Policy::new().with_overflow(Overflow::Wrap).to_string(), "wrap");
354    /// ```
355    #[must_use]
356    pub const fn with_overflow(self, overflow: Overflow) -> Self {
357        Policy((self.0 & !3) | overflow as u8)
358    }
359
360    /// This policy with `div_zero` replaced.
361    ///
362    /// # Examples
363    ///
364    /// ```
365    /// use bytecode_lang::{DivZero, Policy};
366    ///
367    /// assert_eq!(Policy::new().with_div_zero(DivZero::Trap).to_string(), "divtrap");
368    /// ```
369    #[must_use]
370    pub const fn with_div_zero(self, div_zero: DivZero) -> Self {
371        Policy((self.0 & !4) | ((div_zero as u8) << 2))
372    }
373
374    /// This policy with `shift` replaced.
375    ///
376    /// # Examples
377    ///
378    /// ```
379    /// use bytecode_lang::{Policy, Shift};
380    ///
381    /// assert_eq!(Policy::new().with_shift(Shift::Mask).to_string(), "mask");
382    /// ```
383    #[must_use]
384    pub const fn with_shift(self, shift: Shift) -> Self {
385        Policy((self.0 & !0x18) | ((shift as u8) << 3))
386    }
387
388    /// This policy with `float_to_int` replaced.
389    ///
390    /// # Examples
391    ///
392    /// ```
393    /// use bytecode_lang::{FloatToInt, Policy};
394    ///
395    /// let p = Policy::new().with_float_to_int(FloatToInt::Saturate);
396    /// assert_eq!(p.to_string(), "sat");
397    /// ```
398    #[must_use]
399    pub const fn with_float_to_int(self, float_to_int: FloatToInt) -> Self {
400        Policy((self.0 & !32) | ((float_to_int as u8) << 5))
401    }
402
403    /// The packed six-bit form.
404    ///
405    /// # Examples
406    ///
407    /// ```
408    /// use bytecode_lang::Policy;
409    ///
410    /// assert_eq!(Policy::new().bits(), 0);
411    /// ```
412    #[must_use]
413    pub const fn bits(self) -> u8 {
414        self.0
415    }
416
417    /// Unpacks a policy, or `None` if a reserved bit (6–7) is set or the
418    /// shift field holds the reserved code 3. 48 of the 64 six-bit values
419    /// name a policy.
420    ///
421    /// # Examples
422    ///
423    /// ```
424    /// use bytecode_lang::Policy;
425    ///
426    /// assert!(Policy::from_bits(0b11_0111).is_some());
427    /// assert!(Policy::from_bits(0b01_1000).is_none());
428    /// assert!(Policy::from_bits(0b100_0000).is_none());
429    /// ```
430    #[must_use]
431    pub const fn from_bits(bits: u8) -> Option<Self> {
432        if bits & !0x3f != 0 || (bits >> 3) & 3 == 3 {
433            None
434        } else {
435            Some(Policy(bits))
436        }
437    }
438
439    /// Whether every policy is the OPS default (`error`).
440    ///
441    /// # Examples
442    ///
443    /// ```
444    /// use bytecode_lang::{Overflow, Policy};
445    ///
446    /// assert!(Policy::new().is_default());
447    /// assert!(!Policy::new().with_overflow(Overflow::Wrap).is_default());
448    /// ```
449    #[must_use]
450    pub const fn is_default(self) -> bool {
451        self.0 == 0
452    }
453}
454
455impl fmt::Debug for Policy {
456    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
457        f.debug_struct("Policy")
458            .field("overflow", &self.overflow())
459            .field("div_zero", &self.div_zero())
460            .field("shift", &self.shift())
461            .field("float_to_int", &self.float_to_int())
462            .finish()
463    }
464}
465
466/// Prints only the policies that differ from the default, joined by `.`:
467/// `wrap` / `trap` / `promote` (overflow), `divtrap`, `mask` / `shsat` (shift),
468/// `sat` (float-to-int). The default policy prints as the empty string.
469impl fmt::Display for Policy {
470    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
471        let mut sep = "";
472        let mut token = |f: &mut fmt::Formatter<'_>, text: &str| -> fmt::Result {
473            f.write_str(sep)?;
474            sep = ".";
475            f.write_str(text)
476        };
477        match self.overflow() {
478            Overflow::Error => {}
479            Overflow::Wrap => token(f, "wrap")?,
480            Overflow::Trap => token(f, "trap")?,
481            Overflow::Promote => token(f, "promote")?,
482        }
483        if self.div_zero() == DivZero::Trap {
484            token(f, "divtrap")?;
485        }
486        match self.shift() {
487            Shift::Error => {}
488            Shift::Mask => token(f, "mask")?,
489            Shift::Saturate => token(f, "shsat")?,
490        }
491        if self.float_to_int() == FloatToInt::Saturate {
492            token(f, "sat")?;
493        }
494        Ok(())
495    }
496}
497
498/// An integer type plus the policies integer arithmetic consults: the
499/// modifier byte of every typed integer instruction that computes an integer.
500///
501/// Layout: bits 0–2 the [`IntTy`] code, bits 3–4 [`Overflow`], bit 5
502/// [`DivZero`], bits 6–7 [`Shift`] (the value 3 is reserved). The
503/// `float_to_int` policy is not here: no integer arithmetic converts a float,
504/// and the conversions that do carry a [`FloatConv`]. Eight types times
505/// 4 × 2 × 3 policies is 192 valid bytes; the other 64 are refused.
506///
507/// # Examples
508///
509/// ```
510/// use bytecode_lang::{IntOp, IntTy, Overflow, Policy, Shift};
511///
512/// let op = IntOp::new(IntTy::I64).with_policy(Policy::new().with_overflow(Overflow::Wrap));
513/// assert_eq!(op.ty(), IntTy::I64);
514/// assert_eq!(op.policy().overflow(), Overflow::Wrap);
515/// assert_eq!(op.to_string(), "i64.wrap");
516/// assert_eq!(IntOp::from_bits(op.bits()), Some(op));
517/// let php = IntOp::new(IntTy::I64).with_policy(Policy::new().with_shift(Shift::Saturate));
518/// assert_eq!(php.to_string(), "i64.shsat");
519/// assert_eq!(IntOp::from_bits(0b1100_0011), None); // shift code 3 is reserved
520/// ```
521#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
522pub struct IntOp(u8);
523
524impl IntOp {
525    /// An operation at `ty` with the default (all-`error`) policy.
526    ///
527    /// # Examples
528    ///
529    /// ```
530    /// use bytecode_lang::{IntOp, IntTy};
531    ///
532    /// assert_eq!(IntOp::new(IntTy::U8).to_string(), "u8");
533    /// ```
534    #[must_use]
535    pub const fn new(ty: IntTy) -> Self {
536        IntOp(ty as u8)
537    }
538
539    /// This operation with its policy replaced by `policy`'s `overflow`,
540    /// `div_zero`, and `shift` (its `float_to_int`, which no integer
541    /// arithmetic consults, is not carried).
542    ///
543    /// # Examples
544    ///
545    /// ```
546    /// use bytecode_lang::{IntOp, IntTy, Policy, Shift};
547    ///
548    /// let op = IntOp::new(IntTy::I32).with_policy(Policy::new().with_shift(Shift::Mask));
549    /// assert_eq!(op.to_string(), "i32.mask");
550    /// ```
551    #[must_use]
552    pub const fn with_policy(self, policy: Policy) -> Self {
553        let p = policy.bits();
554        // overflow (bits 0-1) and div_zero (bit 2) move up by three; shift
555        // moves from bits 3-4 to bits 6-7.
556        IntOp((self.0 & 7) | ((p & 7) << 3) | (((p >> 3) & 3) << 6))
557    }
558
559    /// The integer type.
560    ///
561    /// # Examples
562    ///
563    /// ```
564    /// use bytecode_lang::{IntOp, IntTy};
565    ///
566    /// assert_eq!(IntOp::new(IntTy::U64).ty(), IntTy::U64);
567    /// ```
568    #[must_use]
569    pub const fn ty(self) -> IntTy {
570        IntTy::from_low3(self.0)
571    }
572
573    /// The policy set: `overflow`, `div_zero`, and `shift` as carried, and
574    /// `float_to_int` at its default (`error`).
575    ///
576    /// # Examples
577    ///
578    /// ```
579    /// use bytecode_lang::{IntOp, IntTy, Policy};
580    ///
581    /// assert_eq!(IntOp::new(IntTy::I8).policy(), Policy::new());
582    /// ```
583    #[must_use]
584    pub const fn policy(self) -> Policy {
585        Policy(((self.0 >> 3) & 7) | ((self.0 >> 6) << 3))
586    }
587
588    /// The packed byte.
589    ///
590    /// # Examples
591    ///
592    /// ```
593    /// use bytecode_lang::{IntOp, IntTy};
594    ///
595    /// assert_eq!(IntOp::new(IntTy::I64).bits(), 3);
596    /// ```
597    #[must_use]
598    pub const fn bits(self) -> u8 {
599        self.0
600    }
601
602    /// Unpacks the byte, or `None` if its shift field holds the reserved
603    /// code 3.
604    ///
605    /// # Examples
606    ///
607    /// ```
608    /// use bytecode_lang::{IntOp, IntTy, Overflow};
609    ///
610    /// let op = IntOp::from_bits(0b0001_1011).unwrap(); // i64, overflow = promote
611    /// assert_eq!((op.ty(), op.policy().overflow()), (IntTy::I64, Overflow::Promote));
612    /// assert!(IntOp::from_bits(0b1100_0000).is_none());
613    /// ```
614    #[must_use]
615    pub const fn from_bits(bits: u8) -> Option<Self> {
616        if bits >> 6 == 3 {
617            None
618        } else {
619            Some(IntOp(bits))
620        }
621    }
622}
623
624impl fmt::Debug for IntOp {
625    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
626        f.debug_struct("IntOp")
627            .field("ty", &self.ty())
628            .field("policy", &self.policy())
629            .finish()
630    }
631}
632
633/// `i64`, followed by `.` and the non-default policies, if any.
634impl fmt::Display for IntOp {
635    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
636        f.write_str(self.ty().name())?;
637        if !self.policy().is_default() {
638            write!(f, ".{}", self.policy())?;
639        }
640        Ok(())
641    }
642}
643
644/// The modifier byte of [`Inst::F32ToInt`](crate::Inst::F32ToInt) and
645/// [`Inst::F64ToInt`](crate::Inst::F64ToInt): the destination integer type
646/// and the `float_to_int` policy for NaN and out-of-range values.
647///
648/// Layout: bits 0–2 the destination [`IntTy`], bit 3 [`FloatToInt`]; bits
649/// 4–7 are zero.
650///
651/// # Examples
652///
653/// ```
654/// use bytecode_lang::{FloatConv, FloatToInt, IntTy};
655///
656/// let c = FloatConv::new(IntTy::I32).with_float_to_int(FloatToInt::Saturate);
657/// assert_eq!((c.ty(), c.float_to_int()), (IntTy::I32, FloatToInt::Saturate));
658/// assert_eq!(c.to_string(), "i32.sat");
659/// assert_eq!(FloatConv::from_bits(c.bits()), Some(c));
660/// assert_eq!(FloatConv::from_bits(0b1_0000), None);
661/// ```
662#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
663pub struct FloatConv(u8);
664
665impl FloatConv {
666    /// A conversion to `ty` with the default policy (`float_to_int = error`).
667    ///
668    /// # Examples
669    ///
670    /// ```
671    /// use bytecode_lang::{FloatConv, IntTy};
672    ///
673    /// assert_eq!(FloatConv::new(IntTy::U8).to_string(), "u8");
674    /// ```
675    #[must_use]
676    pub const fn new(ty: IntTy) -> Self {
677        FloatConv(ty as u8)
678    }
679
680    /// This conversion with its `float_to_int` policy replaced.
681    ///
682    /// # Examples
683    ///
684    /// ```
685    /// use bytecode_lang::{FloatConv, FloatToInt, IntTy};
686    ///
687    /// let c = FloatConv::new(IntTy::I64).with_float_to_int(FloatToInt::Saturate);
688    /// assert_eq!(c.float_to_int(), FloatToInt::Saturate);
689    /// ```
690    #[must_use]
691    pub const fn with_float_to_int(self, float_to_int: FloatToInt) -> Self {
692        FloatConv((self.0 & 7) | ((float_to_int as u8) << 3))
693    }
694
695    /// This conversion with the `float_to_int` part of `policy` (the one
696    /// policy a float-to-integer conversion consults), so a code generator
697    /// can stamp its language's [`Policy`] everywhere.
698    ///
699    /// # Examples
700    ///
701    /// ```
702    /// use bytecode_lang::{FloatConv, FloatToInt, IntTy, Policy};
703    ///
704    /// let p = Policy::new().with_float_to_int(FloatToInt::Saturate);
705    /// assert_eq!(FloatConv::new(IntTy::I8).with_policy(p).to_string(), "i8.sat");
706    /// ```
707    #[must_use]
708    pub const fn with_policy(self, policy: Policy) -> Self {
709        self.with_float_to_int(policy.float_to_int())
710    }
711
712    /// The destination integer type.
713    ///
714    /// # Examples
715    ///
716    /// ```
717    /// use bytecode_lang::{FloatConv, IntTy};
718    ///
719    /// assert_eq!(FloatConv::new(IntTy::U16).ty(), IntTy::U16);
720    /// ```
721    #[must_use]
722    pub const fn ty(self) -> IntTy {
723        IntTy::from_low3(self.0)
724    }
725
726    /// The `float_to_int` policy.
727    ///
728    /// # Examples
729    ///
730    /// ```
731    /// use bytecode_lang::{FloatConv, FloatToInt, IntTy};
732    ///
733    /// assert_eq!(FloatConv::new(IntTy::I8).float_to_int(), FloatToInt::Error);
734    /// ```
735    #[must_use]
736    pub const fn float_to_int(self) -> FloatToInt {
737        if self.0 & 8 == 0 {
738            FloatToInt::Error
739        } else {
740            FloatToInt::Saturate
741        }
742    }
743
744    /// The packed byte.
745    ///
746    /// # Examples
747    ///
748    /// ```
749    /// use bytecode_lang::{FloatConv, IntTy};
750    ///
751    /// assert_eq!(FloatConv::new(IntTy::I64).bits(), 3);
752    /// ```
753    #[must_use]
754    pub const fn bits(self) -> u8 {
755        self.0
756    }
757
758    /// Unpacks the byte, or `None` if any of bits 4–7 is set.
759    ///
760    /// # Examples
761    ///
762    /// ```
763    /// use bytecode_lang::FloatConv;
764    ///
765    /// assert!(FloatConv::from_bits(0b1111).is_some());
766    /// assert!(FloatConv::from_bits(0b1_0000).is_none());
767    /// ```
768    #[must_use]
769    pub const fn from_bits(bits: u8) -> Option<Self> {
770        if bits >> 4 != 0 {
771            None
772        } else {
773            Some(FloatConv(bits))
774        }
775    }
776}
777
778impl fmt::Debug for FloatConv {
779    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
780        f.debug_struct("FloatConv")
781            .field("ty", &self.ty())
782            .field("float_to_int", &self.float_to_int())
783            .finish()
784    }
785}
786
787/// `i64`, then `.sat` when NaN and out-of-range values saturate.
788impl fmt::Display for FloatConv {
789    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
790        f.write_str(self.ty().name())?;
791        if self.float_to_int() == FloatToInt::Saturate {
792            f.write_str(".sat")?;
793        }
794        Ok(())
795    }
796}
797
798/// The modifier byte of [`Inst::IntCast`](crate::Inst::IntCast): source
799/// type, destination type, and the `overflow` policy for values that do not
800/// fit.
801///
802/// Layout: bits 0–2 source [`IntTy`], bits 3–5 destination [`IntTy`], bits
803/// 6–7 [`Overflow`].
804///
805/// # Examples
806///
807/// ```
808/// use bytecode_lang::{IntConv, IntTy, Overflow};
809///
810/// let c = IntConv::new(IntTy::I64, IntTy::U8, Overflow::Wrap);
811/// assert_eq!((c.from(), c.to(), c.overflow()), (IntTy::I64, IntTy::U8, Overflow::Wrap));
812/// assert_eq!(c.to_string(), "i64.u8.wrap");
813/// assert_eq!(IntConv::from_bits(c.bits()), Some(c));
814/// ```
815#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
816pub struct IntConv(u8);
817
818impl IntConv {
819    /// A conversion from `from` to `to` under `overflow`.
820    ///
821    /// # Examples
822    ///
823    /// ```
824    /// use bytecode_lang::{IntConv, IntTy, Overflow};
825    ///
826    /// let c = IntConv::new(IntTy::U32, IntTy::I32, Overflow::Error);
827    /// assert_eq!(c.to_string(), "u32.i32");
828    /// ```
829    #[must_use]
830    pub const fn new(from: IntTy, to: IntTy, overflow: Overflow) -> Self {
831        IntConv((from as u8) | ((to as u8) << 3) | ((overflow as u8) << 6))
832    }
833
834    /// The source type.
835    ///
836    /// # Examples
837    ///
838    /// ```
839    /// use bytecode_lang::{IntConv, IntTy, Overflow};
840    ///
841    /// assert_eq!(IntConv::new(IntTy::I8, IntTy::I16, Overflow::Error).from(), IntTy::I8);
842    /// ```
843    #[must_use]
844    pub const fn from(self) -> IntTy {
845        IntTy::from_low3(self.0)
846    }
847
848    /// The destination type.
849    ///
850    /// # Examples
851    ///
852    /// ```
853    /// use bytecode_lang::{IntConv, IntTy, Overflow};
854    ///
855    /// assert_eq!(IntConv::new(IntTy::I8, IntTy::I16, Overflow::Error).to(), IntTy::I16);
856    /// ```
857    #[must_use]
858    pub const fn to(self) -> IntTy {
859        IntTy::from_low3(self.0 >> 3)
860    }
861
862    /// The overflow policy.
863    ///
864    /// # Examples
865    ///
866    /// ```
867    /// use bytecode_lang::{IntConv, IntTy, Overflow};
868    ///
869    /// let c = IntConv::new(IntTy::I64, IntTy::I8, Overflow::Trap);
870    /// assert_eq!(c.overflow(), Overflow::Trap);
871    /// ```
872    #[must_use]
873    pub const fn overflow(self) -> Overflow {
874        match self.0 >> 6 {
875            0 => Overflow::Error,
876            1 => Overflow::Wrap,
877            2 => Overflow::Trap,
878            _ => Overflow::Promote,
879        }
880    }
881
882    /// The packed byte.
883    ///
884    /// # Examples
885    ///
886    /// ```
887    /// use bytecode_lang::{IntConv, IntTy, Overflow};
888    ///
889    /// assert_eq!(IntConv::new(IntTy::I8, IntTy::I8, Overflow::Error).bits(), 0);
890    /// ```
891    #[must_use]
892    pub const fn bits(self) -> u8 {
893        self.0
894    }
895
896    /// Unpacks the byte. Every byte names a conversion, so this returns
897    /// `Some` for all 256 values in format version 1. (`promote` decodes, but
898    /// `int_cast` always writes a statically typed register, so the builder
899    /// and the verifier refuse it.)
900    ///
901    /// # Examples
902    ///
903    /// ```
904    /// use bytecode_lang::{IntConv, Overflow};
905    ///
906    /// assert_eq!(IntConv::from_bits(0b1100_0000).map(|c| c.overflow()), Some(Overflow::Promote));
907    /// ```
908    #[must_use]
909    pub const fn from_bits(bits: u8) -> Option<Self> {
910        Some(IntConv(bits))
911    }
912}
913
914impl fmt::Debug for IntConv {
915    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
916        f.debug_struct("IntConv")
917            .field("from", &self.from())
918            .field("to", &self.to())
919            .field("overflow", &self.overflow())
920            .finish()
921    }
922}
923
924/// `from.to`, then `.wrap` or `.trap` when the policy is not `error`.
925impl fmt::Display for IntConv {
926    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
927        write!(f, "{}.{}", self.from(), self.to())?;
928        match self.overflow() {
929            Overflow::Error => Ok(()),
930            other => write!(f, ".{other}"),
931        }
932    }
933}
934
935/// The modifier byte of the bit-level conversions
936/// ([`Zext`](crate::Inst::Zext), [`Sext`](crate::Inst::Sext),
937/// [`Trunc`](crate::Inst::Trunc)), which never raise an error and so carry
938/// no policy.
939///
940/// Layout: bits 0–2 source [`IntTy`], bits 3–5 destination [`IntTy`], bits
941/// 6–7 zero.
942///
943/// # Examples
944///
945/// ```
946/// use bytecode_lang::{IntPair, IntTy};
947///
948/// let p = IntPair::new(IntTy::I8, IntTy::I64);
949/// assert_eq!((p.from(), p.to()), (IntTy::I8, IntTy::I64));
950/// assert_eq!(p.to_string(), "i8.i64");
951/// assert_eq!(IntPair::from_bits(0b0100_0000), None);
952/// ```
953#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
954pub struct IntPair(u8);
955
956impl IntPair {
957    /// The pair `from` → `to`.
958    ///
959    /// # Examples
960    ///
961    /// ```
962    /// use bytecode_lang::{IntPair, IntTy};
963    ///
964    /// assert_eq!(IntPair::new(IntTy::U8, IntTy::U32).to(), IntTy::U32);
965    /// ```
966    #[must_use]
967    pub const fn new(from: IntTy, to: IntTy) -> Self {
968        IntPair((from as u8) | ((to as u8) << 3))
969    }
970
971    /// The source type.
972    ///
973    /// # Examples
974    ///
975    /// ```
976    /// use bytecode_lang::{IntPair, IntTy};
977    ///
978    /// assert_eq!(IntPair::new(IntTy::U8, IntTy::U32).from(), IntTy::U8);
979    /// ```
980    #[must_use]
981    pub const fn from(self) -> IntTy {
982        IntTy::from_low3(self.0)
983    }
984
985    /// The destination type.
986    ///
987    /// # Examples
988    ///
989    /// ```
990    /// use bytecode_lang::{IntPair, IntTy};
991    ///
992    /// assert_eq!(IntPair::new(IntTy::I64, IntTy::I16).to(), IntTy::I16);
993    /// ```
994    #[must_use]
995    pub const fn to(self) -> IntTy {
996        IntTy::from_low3(self.0 >> 3)
997    }
998
999    /// The packed byte.
1000    ///
1001    /// # Examples
1002    ///
1003    /// ```
1004    /// use bytecode_lang::{IntPair, IntTy};
1005    ///
1006    /// assert_eq!(IntPair::new(IntTy::I16, IntTy::I16).bits(), 0b001_001);
1007    /// ```
1008    #[must_use]
1009    pub const fn bits(self) -> u8 {
1010        self.0
1011    }
1012
1013    /// Unpacks the byte, or `None` if bit 6 or 7 is set.
1014    ///
1015    /// # Examples
1016    ///
1017    /// ```
1018    /// use bytecode_lang::IntPair;
1019    ///
1020    /// assert!(IntPair::from_bits(0b0011_1111).is_some());
1021    /// assert!(IntPair::from_bits(0b1000_0000).is_none());
1022    /// ```
1023    #[must_use]
1024    pub const fn from_bits(bits: u8) -> Option<Self> {
1025        if bits >> 6 != 0 {
1026            None
1027        } else {
1028            Some(IntPair(bits))
1029        }
1030    }
1031}
1032
1033impl fmt::Debug for IntPair {
1034    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1035        f.debug_struct("IntPair")
1036            .field("from", &self.from())
1037            .field("to", &self.to())
1038            .finish()
1039    }
1040}
1041
1042/// `from.to`.
1043impl fmt::Display for IntPair {
1044    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1045        write!(f, "{}.{}", self.from(), self.to())
1046    }
1047}
1048
1049#[cfg(test)]
1050mod tests {
1051    use alloc::string::ToString;
1052
1053    use super::*;
1054
1055    #[test]
1056    fn policy_accepts_exactly_the_valid_bit_patterns() {
1057        let mut accepted = 0;
1058        for bits in 0..=u8::MAX {
1059            if let Some(p) = Policy::from_bits(bits) {
1060                accepted += 1;
1061                assert_eq!(p.bits(), bits);
1062                let rebuilt = Policy::new()
1063                    .with_overflow(p.overflow())
1064                    .with_div_zero(p.div_zero())
1065                    .with_shift(p.shift())
1066                    .with_float_to_int(p.float_to_int());
1067                assert_eq!(rebuilt, p);
1068            }
1069        }
1070        // 4 overflow x 2 div_zero x 3 shift x 2 float_to_int.
1071        assert_eq!(accepted, 48);
1072    }
1073
1074    #[test]
1075    fn int_op_round_trips_every_valid_byte() {
1076        let mut accepted = 0;
1077        for bits in 0..=u8::MAX {
1078            if let Some(op) = IntOp::from_bits(bits) {
1079                accepted += 1;
1080                assert_eq!(IntOp::new(op.ty()).with_policy(op.policy()), op);
1081                assert_eq!(op.policy().float_to_int(), FloatToInt::Error);
1082            }
1083        }
1084        // 8 types x 4 overflow x 2 div_zero x 3 shift.
1085        assert_eq!(accepted, 8 * 24);
1086    }
1087
1088    #[test]
1089    fn int_op_keeps_every_policy_but_float_to_int() {
1090        for bits in 0..=u8::MAX {
1091            let Some(p) = Policy::from_bits(bits) else {
1092                continue;
1093            };
1094            for &ty in IntTy::ALL {
1095                let op = IntOp::new(ty).with_policy(p);
1096                assert_eq!(op.ty(), ty);
1097                assert_eq!(op.policy(), p.with_float_to_int(FloatToInt::Error));
1098            }
1099        }
1100    }
1101
1102    #[test]
1103    fn float_conv_round_trips_every_valid_byte() {
1104        let mut accepted = 0;
1105        for bits in 0..=u8::MAX {
1106            if let Some(c) = FloatConv::from_bits(bits) {
1107                accepted += 1;
1108                assert_eq!(
1109                    FloatConv::new(c.ty()).with_float_to_int(c.float_to_int()),
1110                    c
1111                );
1112            }
1113        }
1114        assert_eq!(accepted, 16);
1115    }
1116
1117    #[test]
1118    fn int_conv_and_pair_round_trip() {
1119        for bits in 0..=u8::MAX {
1120            if let Some(c) = IntConv::from_bits(bits) {
1121                assert_eq!(IntConv::new(c.from(), c.to(), c.overflow()), c);
1122            }
1123            if let Some(p) = IntPair::from_bits(bits) {
1124                assert_eq!(IntPair::new(p.from(), p.to()), p);
1125            }
1126        }
1127    }
1128
1129    #[test]
1130    fn display_lists_only_non_default_policies() {
1131        assert_eq!(Policy::new().to_string(), "");
1132        let all = Policy::new()
1133            .with_overflow(Overflow::Trap)
1134            .with_div_zero(DivZero::Trap)
1135            .with_shift(Shift::Mask)
1136            .with_float_to_int(FloatToInt::Saturate);
1137        assert_eq!(all.to_string(), "trap.divtrap.mask.sat");
1138        let php = Policy::new()
1139            .with_overflow(Overflow::Promote)
1140            .with_shift(Shift::Saturate);
1141        assert_eq!(php.to_string(), "promote.shsat");
1142        assert_eq!(Policy::from_bits(php.bits()), Some(php));
1143        let promote = Policy::new().with_overflow(Overflow::Promote);
1144        assert_eq!(promote.to_string(), "promote");
1145        assert_eq!(Policy::from_bits(promote.bits()), Some(promote));
1146        assert_eq!(
1147            IntOp::new(IntTy::U64).with_policy(all).to_string(),
1148            "u64.trap.divtrap.mask"
1149        );
1150    }
1151
1152    #[test]
1153    fn int_types_report_width_and_sign() {
1154        let widths: [u32; 8] = [8, 16, 32, 64, 8, 16, 32, 64];
1155        for (ty, bits) in IntTy::ALL.iter().zip(widths) {
1156            assert_eq!(ty.bits(), bits);
1157            assert_eq!(ty.is_signed(), ty.code() < 4);
1158            assert_eq!(IntTy::from_low3(ty.code()), *ty);
1159        }
1160    }
1161}