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}