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}