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    /// ```
186    #[derive(Default)]
187    Shift {
188        /// Raise `ShiftOutOfRange` (E0003) through the language's error model.
189        #[default]
190        Error = 0 => "error",
191        /// Use the amount modulo the width (`n & (width - 1)`); never an error.
192        Mask = 1 => "mask",
193    }
194}
195
196code_enum! {
197    /// The `float_to_int` policy: what float-to-integer conversion does with
198    /// NaN or an out-of-range value.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// use bytecode_lang::FloatToInt;
204    ///
205    /// assert_eq!(FloatToInt::default(), FloatToInt::Error);
206    /// ```
207    #[derive(Default)]
208    FloatToInt {
209        /// Raise `InvalidConversion` (E0004) through the language's error model.
210        #[default]
211        Error = 0 => "error",
212        /// Clamp to the type's MIN/MAX; NaN becomes 0.
213        Saturate = 1 => "saturate",
214    }
215}
216
217/// The complete OPS policy set, packed into five bits.
218///
219/// Layout: bits 0–1 `overflow`, bit 2 `div_zero`, bit 3 `shift`, bit 4
220/// `float_to_int`; bits 5–7 are zero. An instruction carries the whole set
221/// even when only part of it can apply (an `iadd` never divides), so a code
222/// generator stamps one policy per language and type onto every instruction
223/// and the semantics pick the part they need. The default is all-`error`,
224/// the OPS default.
225///
226/// # Examples
227///
228/// ```
229/// use bytecode_lang::{Overflow, Policy, Shift};
230///
231/// let p = Policy::new().with_overflow(Overflow::Wrap).with_shift(Shift::Mask);
232/// assert_eq!(p.overflow(), Overflow::Wrap);
233/// assert_eq!(p.shift(), Shift::Mask);
234/// assert_eq!(p.to_string(), "wrap.mask");
235/// assert_eq!(Policy::from_bits(p.bits()), Some(p));
236/// assert_eq!(Policy::from_bits(0b11).map(|p| p.overflow()), Some(Overflow::Promote));
237/// assert_eq!(Policy::from_bits(0b10_0000), None); // bit 5 is reserved
238/// ```
239#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
240pub struct Policy(u8);
241
242impl Policy {
243    /// The OPS defaults: every policy is `error`.
244    pub const DEFAULT: Policy = Policy(0);
245
246    /// The OPS defaults: every policy is `error`.
247    ///
248    /// # Examples
249    ///
250    /// ```
251    /// use bytecode_lang::{Overflow, Policy};
252    ///
253    /// assert_eq!(Policy::new().overflow(), Overflow::Error);
254    /// ```
255    #[must_use]
256    pub const fn new() -> Self {
257        Policy(0)
258    }
259
260    /// The `overflow` policy.
261    ///
262    /// # Examples
263    ///
264    /// ```
265    /// use bytecode_lang::{Overflow, Policy};
266    ///
267    /// assert_eq!(Policy::new().with_overflow(Overflow::Trap).overflow(), Overflow::Trap);
268    /// ```
269    #[must_use]
270    pub const fn overflow(self) -> Overflow {
271        match self.0 & 3 {
272            0 => Overflow::Error,
273            1 => Overflow::Wrap,
274            2 => Overflow::Trap,
275            _ => Overflow::Promote,
276        }
277    }
278
279    /// The `div_zero` policy.
280    ///
281    /// # Examples
282    ///
283    /// ```
284    /// use bytecode_lang::{DivZero, Policy};
285    ///
286    /// assert_eq!(Policy::new().with_div_zero(DivZero::Trap).div_zero(), DivZero::Trap);
287    /// ```
288    #[must_use]
289    pub const fn div_zero(self) -> DivZero {
290        if self.0 & 4 == 0 {
291            DivZero::Error
292        } else {
293            DivZero::Trap
294        }
295    }
296
297    /// The `shift` policy.
298    ///
299    /// # Examples
300    ///
301    /// ```
302    /// use bytecode_lang::{Policy, Shift};
303    ///
304    /// assert_eq!(Policy::new().with_shift(Shift::Mask).shift(), Shift::Mask);
305    /// ```
306    #[must_use]
307    pub const fn shift(self) -> Shift {
308        if self.0 & 8 == 0 {
309            Shift::Error
310        } else {
311            Shift::Mask
312        }
313    }
314
315    /// The `float_to_int` policy.
316    ///
317    /// # Examples
318    ///
319    /// ```
320    /// use bytecode_lang::{FloatToInt, Policy};
321    ///
322    /// let p = Policy::new().with_float_to_int(FloatToInt::Saturate);
323    /// assert_eq!(p.float_to_int(), FloatToInt::Saturate);
324    /// ```
325    #[must_use]
326    pub const fn float_to_int(self) -> FloatToInt {
327        if self.0 & 16 == 0 {
328            FloatToInt::Error
329        } else {
330            FloatToInt::Saturate
331        }
332    }
333
334    /// This policy with `overflow` replaced.
335    ///
336    /// # Examples
337    ///
338    /// ```
339    /// use bytecode_lang::{Overflow, Policy};
340    ///
341    /// assert_eq!(Policy::new().with_overflow(Overflow::Wrap).to_string(), "wrap");
342    /// ```
343    #[must_use]
344    pub const fn with_overflow(self, overflow: Overflow) -> Self {
345        Policy((self.0 & !3) | overflow as u8)
346    }
347
348    /// This policy with `div_zero` replaced.
349    ///
350    /// # Examples
351    ///
352    /// ```
353    /// use bytecode_lang::{DivZero, Policy};
354    ///
355    /// assert_eq!(Policy::new().with_div_zero(DivZero::Trap).to_string(), "divtrap");
356    /// ```
357    #[must_use]
358    pub const fn with_div_zero(self, div_zero: DivZero) -> Self {
359        Policy((self.0 & !4) | ((div_zero as u8) << 2))
360    }
361
362    /// This policy with `shift` replaced.
363    ///
364    /// # Examples
365    ///
366    /// ```
367    /// use bytecode_lang::{Policy, Shift};
368    ///
369    /// assert_eq!(Policy::new().with_shift(Shift::Mask).to_string(), "mask");
370    /// ```
371    #[must_use]
372    pub const fn with_shift(self, shift: Shift) -> Self {
373        Policy((self.0 & !8) | ((shift as u8) << 3))
374    }
375
376    /// This policy with `float_to_int` replaced.
377    ///
378    /// # Examples
379    ///
380    /// ```
381    /// use bytecode_lang::{FloatToInt, Policy};
382    ///
383    /// let p = Policy::new().with_float_to_int(FloatToInt::Saturate);
384    /// assert_eq!(p.to_string(), "sat");
385    /// ```
386    #[must_use]
387    pub const fn with_float_to_int(self, float_to_int: FloatToInt) -> Self {
388        Policy((self.0 & !16) | ((float_to_int as u8) << 4))
389    }
390
391    /// The packed five-bit form.
392    ///
393    /// # Examples
394    ///
395    /// ```
396    /// use bytecode_lang::Policy;
397    ///
398    /// assert_eq!(Policy::new().bits(), 0);
399    /// ```
400    #[must_use]
401    pub const fn bits(self) -> u8 {
402        self.0
403    }
404
405    /// Unpacks a policy, or `None` if a reserved bit (5–7) is set. Every
406    /// 5-bit value names a policy.
407    ///
408    /// # Examples
409    ///
410    /// ```
411    /// use bytecode_lang::Policy;
412    ///
413    /// assert!(Policy::from_bits(0b1_1111).is_some());
414    /// assert!(Policy::from_bits(0b10_0000).is_none());
415    /// ```
416    #[must_use]
417    pub const fn from_bits(bits: u8) -> Option<Self> {
418        if bits & !0x1f != 0 {
419            None
420        } else {
421            Some(Policy(bits))
422        }
423    }
424
425    /// Whether every policy is the OPS default (`error`).
426    ///
427    /// # Examples
428    ///
429    /// ```
430    /// use bytecode_lang::{Overflow, Policy};
431    ///
432    /// assert!(Policy::new().is_default());
433    /// assert!(!Policy::new().with_overflow(Overflow::Wrap).is_default());
434    /// ```
435    #[must_use]
436    pub const fn is_default(self) -> bool {
437        self.0 == 0
438    }
439}
440
441impl fmt::Debug for Policy {
442    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
443        f.debug_struct("Policy")
444            .field("overflow", &self.overflow())
445            .field("div_zero", &self.div_zero())
446            .field("shift", &self.shift())
447            .field("float_to_int", &self.float_to_int())
448            .finish()
449    }
450}
451
452/// Prints only the policies that differ from the default, joined by `.`:
453/// `wrap` / `trap` / `promote` (overflow), `divtrap`, `mask`, `sat`. The default policy
454/// prints as the empty string.
455impl fmt::Display for Policy {
456    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
457        let mut sep = "";
458        let mut token = |f: &mut fmt::Formatter<'_>, text: &str| -> fmt::Result {
459            f.write_str(sep)?;
460            sep = ".";
461            f.write_str(text)
462        };
463        match self.overflow() {
464            Overflow::Error => {}
465            Overflow::Wrap => token(f, "wrap")?,
466            Overflow::Trap => token(f, "trap")?,
467            Overflow::Promote => token(f, "promote")?,
468        }
469        if self.div_zero() == DivZero::Trap {
470            token(f, "divtrap")?;
471        }
472        if self.shift() == Shift::Mask {
473            token(f, "mask")?;
474        }
475        if self.float_to_int() == FloatToInt::Saturate {
476            token(f, "sat")?;
477        }
478        Ok(())
479    }
480}
481
482/// An integer type plus the complete policy set: the modifier byte of every
483/// integer instruction that computes an integer.
484///
485/// Layout: bits 0–2 the [`IntTy`] code, bits 3–7 the [`Policy`].
486///
487/// # Examples
488///
489/// ```
490/// use bytecode_lang::{IntOp, IntTy, Overflow, Policy};
491///
492/// let op = IntOp::new(IntTy::I64).with_policy(Policy::new().with_overflow(Overflow::Wrap));
493/// assert_eq!(op.ty(), IntTy::I64);
494/// assert_eq!(op.policy().overflow(), Overflow::Wrap);
495/// assert_eq!(op.to_string(), "i64.wrap");
496/// assert_eq!(IntOp::from_bits(op.bits()), Some(op));
497/// ```
498#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
499pub struct IntOp(u8);
500
501impl IntOp {
502    /// An operation at `ty` with the default (all-`error`) policy.
503    ///
504    /// # Examples
505    ///
506    /// ```
507    /// use bytecode_lang::{IntOp, IntTy};
508    ///
509    /// assert_eq!(IntOp::new(IntTy::U8).to_string(), "u8");
510    /// ```
511    #[must_use]
512    pub const fn new(ty: IntTy) -> Self {
513        IntOp(ty as u8)
514    }
515
516    /// This operation with its policy replaced.
517    ///
518    /// # Examples
519    ///
520    /// ```
521    /// use bytecode_lang::{IntOp, IntTy, Policy, Shift};
522    ///
523    /// let op = IntOp::new(IntTy::I32).with_policy(Policy::new().with_shift(Shift::Mask));
524    /// assert_eq!(op.to_string(), "i32.mask");
525    /// ```
526    #[must_use]
527    pub const fn with_policy(self, policy: Policy) -> Self {
528        IntOp((self.0 & 7) | (policy.bits() << 3))
529    }
530
531    /// The integer type.
532    ///
533    /// # Examples
534    ///
535    /// ```
536    /// use bytecode_lang::{IntOp, IntTy};
537    ///
538    /// assert_eq!(IntOp::new(IntTy::U64).ty(), IntTy::U64);
539    /// ```
540    #[must_use]
541    pub const fn ty(self) -> IntTy {
542        IntTy::from_low3(self.0)
543    }
544
545    /// The policy set.
546    ///
547    /// # Examples
548    ///
549    /// ```
550    /// use bytecode_lang::{IntOp, IntTy, Policy};
551    ///
552    /// assert_eq!(IntOp::new(IntTy::I8).policy(), Policy::new());
553    /// ```
554    #[must_use]
555    pub const fn policy(self) -> Policy {
556        Policy(self.0 >> 3)
557    }
558
559    /// The packed byte.
560    ///
561    /// # Examples
562    ///
563    /// ```
564    /// use bytecode_lang::{IntOp, IntTy};
565    ///
566    /// assert_eq!(IntOp::new(IntTy::I64).bits(), 3);
567    /// ```
568    #[must_use]
569    pub const fn bits(self) -> u8 {
570        self.0
571    }
572
573    /// Unpacks the byte. Every byte is an integer type plus a policy, so this
574    /// returns `Some` for all 256 values in format version 1; the `Option`
575    /// leaves room for reserved bits in a later version.
576    ///
577    /// # Examples
578    ///
579    /// ```
580    /// use bytecode_lang::{IntOp, IntTy, Overflow};
581    ///
582    /// let op = IntOp::from_bits(0b0001_1011).unwrap(); // i64, overflow = promote
583    /// assert_eq!((op.ty(), op.policy().overflow()), (IntTy::I64, Overflow::Promote));
584    /// ```
585    #[must_use]
586    pub const fn from_bits(bits: u8) -> Option<Self> {
587        match Policy::from_bits(bits >> 3) {
588            Some(_) => Some(IntOp(bits)),
589            None => None,
590        }
591    }
592}
593
594impl fmt::Debug for IntOp {
595    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596        f.debug_struct("IntOp")
597            .field("ty", &self.ty())
598            .field("policy", &self.policy())
599            .finish()
600    }
601}
602
603/// `i64`, followed by `.` and the non-default policies, if any.
604impl fmt::Display for IntOp {
605    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
606        f.write_str(self.ty().name())?;
607        if !self.policy().is_default() {
608            write!(f, ".{}", self.policy())?;
609        }
610        Ok(())
611    }
612}
613
614/// The modifier byte of [`Inst::IntCast`](crate::Inst::IntCast): source
615/// type, destination type, and the `overflow` policy for values that do not
616/// fit.
617///
618/// Layout: bits 0–2 source [`IntTy`], bits 3–5 destination [`IntTy`], bits
619/// 6–7 [`Overflow`].
620///
621/// # Examples
622///
623/// ```
624/// use bytecode_lang::{IntConv, IntTy, Overflow};
625///
626/// let c = IntConv::new(IntTy::I64, IntTy::U8, Overflow::Wrap);
627/// assert_eq!((c.from(), c.to(), c.overflow()), (IntTy::I64, IntTy::U8, Overflow::Wrap));
628/// assert_eq!(c.to_string(), "i64.u8.wrap");
629/// assert_eq!(IntConv::from_bits(c.bits()), Some(c));
630/// ```
631#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
632pub struct IntConv(u8);
633
634impl IntConv {
635    /// A conversion from `from` to `to` under `overflow`.
636    ///
637    /// # Examples
638    ///
639    /// ```
640    /// use bytecode_lang::{IntConv, IntTy, Overflow};
641    ///
642    /// let c = IntConv::new(IntTy::U32, IntTy::I32, Overflow::Error);
643    /// assert_eq!(c.to_string(), "u32.i32");
644    /// ```
645    #[must_use]
646    pub const fn new(from: IntTy, to: IntTy, overflow: Overflow) -> Self {
647        IntConv((from as u8) | ((to as u8) << 3) | ((overflow as u8) << 6))
648    }
649
650    /// The source type.
651    ///
652    /// # Examples
653    ///
654    /// ```
655    /// use bytecode_lang::{IntConv, IntTy, Overflow};
656    ///
657    /// assert_eq!(IntConv::new(IntTy::I8, IntTy::I16, Overflow::Error).from(), IntTy::I8);
658    /// ```
659    #[must_use]
660    pub const fn from(self) -> IntTy {
661        IntTy::from_low3(self.0)
662    }
663
664    /// The destination type.
665    ///
666    /// # Examples
667    ///
668    /// ```
669    /// use bytecode_lang::{IntConv, IntTy, Overflow};
670    ///
671    /// assert_eq!(IntConv::new(IntTy::I8, IntTy::I16, Overflow::Error).to(), IntTy::I16);
672    /// ```
673    #[must_use]
674    pub const fn to(self) -> IntTy {
675        IntTy::from_low3(self.0 >> 3)
676    }
677
678    /// The overflow policy.
679    ///
680    /// # Examples
681    ///
682    /// ```
683    /// use bytecode_lang::{IntConv, IntTy, Overflow};
684    ///
685    /// let c = IntConv::new(IntTy::I64, IntTy::I8, Overflow::Trap);
686    /// assert_eq!(c.overflow(), Overflow::Trap);
687    /// ```
688    #[must_use]
689    pub const fn overflow(self) -> Overflow {
690        match self.0 >> 6 {
691            0 => Overflow::Error,
692            1 => Overflow::Wrap,
693            2 => Overflow::Trap,
694            _ => Overflow::Promote,
695        }
696    }
697
698    /// The packed byte.
699    ///
700    /// # Examples
701    ///
702    /// ```
703    /// use bytecode_lang::{IntConv, IntTy, Overflow};
704    ///
705    /// assert_eq!(IntConv::new(IntTy::I8, IntTy::I8, Overflow::Error).bits(), 0);
706    /// ```
707    #[must_use]
708    pub const fn bits(self) -> u8 {
709        self.0
710    }
711
712    /// Unpacks the byte. Every byte names a conversion, so this returns
713    /// `Some` for all 256 values in format version 1. (`promote` decodes, but
714    /// `int_cast` always writes a statically typed register, so the builder
715    /// and the verifier refuse it.)
716    ///
717    /// # Examples
718    ///
719    /// ```
720    /// use bytecode_lang::{IntConv, Overflow};
721    ///
722    /// assert_eq!(IntConv::from_bits(0b1100_0000).map(|c| c.overflow()), Some(Overflow::Promote));
723    /// ```
724    #[must_use]
725    pub const fn from_bits(bits: u8) -> Option<Self> {
726        Some(IntConv(bits))
727    }
728}
729
730impl fmt::Debug for IntConv {
731    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
732        f.debug_struct("IntConv")
733            .field("from", &self.from())
734            .field("to", &self.to())
735            .field("overflow", &self.overflow())
736            .finish()
737    }
738}
739
740/// `from.to`, then `.wrap` or `.trap` when the policy is not `error`.
741impl fmt::Display for IntConv {
742    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
743        write!(f, "{}.{}", self.from(), self.to())?;
744        match self.overflow() {
745            Overflow::Error => Ok(()),
746            other => write!(f, ".{other}"),
747        }
748    }
749}
750
751/// The modifier byte of the bit-level conversions
752/// ([`Zext`](crate::Inst::Zext), [`Sext`](crate::Inst::Sext),
753/// [`Trunc`](crate::Inst::Trunc)), which never raise an error and so carry
754/// no policy.
755///
756/// Layout: bits 0–2 source [`IntTy`], bits 3–5 destination [`IntTy`], bits
757/// 6–7 zero.
758///
759/// # Examples
760///
761/// ```
762/// use bytecode_lang::{IntPair, IntTy};
763///
764/// let p = IntPair::new(IntTy::I8, IntTy::I64);
765/// assert_eq!((p.from(), p.to()), (IntTy::I8, IntTy::I64));
766/// assert_eq!(p.to_string(), "i8.i64");
767/// assert_eq!(IntPair::from_bits(0b0100_0000), None);
768/// ```
769#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
770pub struct IntPair(u8);
771
772impl IntPair {
773    /// The pair `from` → `to`.
774    ///
775    /// # Examples
776    ///
777    /// ```
778    /// use bytecode_lang::{IntPair, IntTy};
779    ///
780    /// assert_eq!(IntPair::new(IntTy::U8, IntTy::U32).to(), IntTy::U32);
781    /// ```
782    #[must_use]
783    pub const fn new(from: IntTy, to: IntTy) -> Self {
784        IntPair((from as u8) | ((to as u8) << 3))
785    }
786
787    /// The source type.
788    ///
789    /// # Examples
790    ///
791    /// ```
792    /// use bytecode_lang::{IntPair, IntTy};
793    ///
794    /// assert_eq!(IntPair::new(IntTy::U8, IntTy::U32).from(), IntTy::U8);
795    /// ```
796    #[must_use]
797    pub const fn from(self) -> IntTy {
798        IntTy::from_low3(self.0)
799    }
800
801    /// The destination type.
802    ///
803    /// # Examples
804    ///
805    /// ```
806    /// use bytecode_lang::{IntPair, IntTy};
807    ///
808    /// assert_eq!(IntPair::new(IntTy::I64, IntTy::I16).to(), IntTy::I16);
809    /// ```
810    #[must_use]
811    pub const fn to(self) -> IntTy {
812        IntTy::from_low3(self.0 >> 3)
813    }
814
815    /// The packed byte.
816    ///
817    /// # Examples
818    ///
819    /// ```
820    /// use bytecode_lang::{IntPair, IntTy};
821    ///
822    /// assert_eq!(IntPair::new(IntTy::I16, IntTy::I16).bits(), 0b001_001);
823    /// ```
824    #[must_use]
825    pub const fn bits(self) -> u8 {
826        self.0
827    }
828
829    /// Unpacks the byte, or `None` if bit 6 or 7 is set.
830    ///
831    /// # Examples
832    ///
833    /// ```
834    /// use bytecode_lang::IntPair;
835    ///
836    /// assert!(IntPair::from_bits(0b0011_1111).is_some());
837    /// assert!(IntPair::from_bits(0b1000_0000).is_none());
838    /// ```
839    #[must_use]
840    pub const fn from_bits(bits: u8) -> Option<Self> {
841        if bits >> 6 != 0 {
842            None
843        } else {
844            Some(IntPair(bits))
845        }
846    }
847}
848
849impl fmt::Debug for IntPair {
850    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
851        f.debug_struct("IntPair")
852            .field("from", &self.from())
853            .field("to", &self.to())
854            .finish()
855    }
856}
857
858/// `from.to`.
859impl fmt::Display for IntPair {
860    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
861        write!(f, "{}.{}", self.from(), self.to())
862    }
863}
864
865#[cfg(test)]
866mod tests {
867    use alloc::string::ToString;
868
869    use super::*;
870
871    #[test]
872    fn policy_accepts_exactly_the_valid_bit_patterns() {
873        let mut accepted = 0;
874        for bits in 0..=u8::MAX {
875            if let Some(p) = Policy::from_bits(bits) {
876                accepted += 1;
877                assert_eq!(p.bits(), bits);
878                let rebuilt = Policy::new()
879                    .with_overflow(p.overflow())
880                    .with_div_zero(p.div_zero())
881                    .with_shift(p.shift())
882                    .with_float_to_int(p.float_to_int());
883                assert_eq!(rebuilt, p);
884            }
885        }
886        // 4 overflow x 2 div_zero x 2 shift x 2 float_to_int.
887        assert_eq!(accepted, 32);
888    }
889
890    #[test]
891    fn int_op_round_trips_every_valid_byte() {
892        let mut accepted = 0;
893        for bits in 0..=u8::MAX {
894            if let Some(op) = IntOp::from_bits(bits) {
895                accepted += 1;
896                assert_eq!(IntOp::new(op.ty()).with_policy(op.policy()), op);
897            }
898        }
899        assert_eq!(accepted, 8 * 32);
900    }
901
902    #[test]
903    fn int_conv_and_pair_round_trip() {
904        for bits in 0..=u8::MAX {
905            if let Some(c) = IntConv::from_bits(bits) {
906                assert_eq!(IntConv::new(c.from(), c.to(), c.overflow()), c);
907            }
908            if let Some(p) = IntPair::from_bits(bits) {
909                assert_eq!(IntPair::new(p.from(), p.to()), p);
910            }
911        }
912    }
913
914    #[test]
915    fn display_lists_only_non_default_policies() {
916        assert_eq!(Policy::new().to_string(), "");
917        let all = Policy::new()
918            .with_overflow(Overflow::Trap)
919            .with_div_zero(DivZero::Trap)
920            .with_shift(Shift::Mask)
921            .with_float_to_int(FloatToInt::Saturate);
922        assert_eq!(all.to_string(), "trap.divtrap.mask.sat");
923        let promote = Policy::new().with_overflow(Overflow::Promote);
924        assert_eq!(promote.to_string(), "promote");
925        assert_eq!(Policy::from_bits(promote.bits()), Some(promote));
926        assert_eq!(
927            IntOp::new(IntTy::U64).with_policy(all).to_string(),
928            "u64.trap.divtrap.mask.sat"
929        );
930    }
931
932    #[test]
933    fn int_types_report_width_and_sign() {
934        let widths: [u32; 8] = [8, 16, 32, 64, 8, 16, 32, 64];
935        for (ty, bits) in IntTy::ALL.iter().zip(widths) {
936            assert_eq!(ty.bits(), bits);
937            assert_eq!(ty.is_signed(), ty.code() < 4);
938            assert_eq!(IntTy::from_low3(ty.code()), *ty);
939        }
940    }
941}