Skip to main content

bamts_native/
lib.rs

1//! Native ABI foundations for BamTiScript.
2//!
3//! This module owns the C-layout value representation shared verbatim between
4//! the register interpreter and generated native code. `Value` constants and
5//! `ShadowFrame` layout are grounded in the machine-checked formal models:
6//!
7//! * `formal/lean/Bamti/Value.lean` — the NaN-boxed `Word64` field algebra
8//!   (`header:13 || tag:3 || payload:48`), the seven nonzero tags, and the
9//!   `encode`/`decode` round-trip theorem.
10//! * `formal/lean/Bamti/Abi.lean` — the 32-byte, 8-aligned `ShadowFrame`
11//!   header layout theorem.
12//!
13//! `Completion` and `CompletionTag` belong to the native-entry contract in the
14//! canonical execution plan (N5); the Lean files do not assign them a wire
15//! layout. `Bamti.NodeLoop.Completion` is a separate event-loop proof record.
16//!
17//! The value/frame primitives in this file are total, allocation-free, and
18//! require no `unsafe`. The native runtime bridge — the exported `bamts_*`
19//! helper ABI, the panic- and nesting-safe [`native_bridge::NativeOps`]
20//! dispatch seam, and the feature-gated JIT/AOT linkage surfaces — lives in
21//! [`native_bridge`], which centralizes every `unsafe` operation the generated
22//! code requires.
23
24use core::num::{NonZeroU16, NonZeroU32};
25
26// -- NaN-box field constants (grounded in Value.lean) ------------------------
27
28/// Header bits 63..51. `canonicalHeader = 4095`, so `4095 << 51`.
29const HEADER_SHIFT: u32 = 51;
30
31/// Tag bits 50..48, immediately below the 13-bit header.
32const TAG_SHIFT: u32 = 48;
33
34/// The three-bit tag field mask.
35const TAG_MASK: u64 = 0b111;
36
37/// The low 48 payload bits (`payloadToNat` domain).
38const PAYLOAD_MASK: u64 = (1u64 << 48) - 1;
39
40/// The `upper : u16` half of the payload, bits 47..32 (`packSlot` segment).
41const UPPER_MASK: u64 = 0xFFFF_0000_0000;
42
43/// The full 13-bit header field mask, bits 63..51.
44const HEADER_MASK: u64 = 0x1FFFu64 << HEADER_SHIFT;
45
46/// The canonical positive quiet NaN, `0x7ff8_0000_0000_0000`.
47const CANON_NAN: u64 = 4095u64 << HEADER_SHIFT;
48
49/// Tag code for a heap reference (`SlotId`).
50pub const TAG_HEAP_REF: u8 = 1;
51/// Tag code for a boxed 32-bit integer.
52pub const TAG_INT32: u8 = 2;
53/// Tag code for `undefined`.
54pub const TAG_UNDEFINED: u8 = 3;
55/// Tag code for `null`.
56pub const TAG_NULL: u8 = 4;
57/// Tag code for a boolean.
58pub const TAG_BOOLEAN: u8 = 5;
59/// Tag code for the array/TDZ hole.
60pub const TAG_HOLE: u8 = 6;
61/// Tag code for an uninitialized register slot.
62pub const TAG_UNINITIALIZED: u8 = 7;
63
64#[inline]
65const fn tag_of(bits: u64) -> u64 {
66    (bits >> TAG_SHIFT) & TAG_MASK
67}
68
69/// A word is boxed when it carries the canonical NaN header and a nonzero tag
70/// (`isBoxedWire`). Every non-boxed word is an arithmetic double.
71#[inline]
72const fn is_boxed(bits: u64) -> bool {
73    (bits & HEADER_MASK) == CANON_NAN && tag_of(bits) != 0
74}
75
76/// `CANON_NAN | (tag << 48) | payload`, matching `boxedWord`.
77///
78/// `tag` must be one of the seven codes (`< 8`) and `payload` must fit in 48
79/// bits; both hold for every internal caller.
80#[inline]
81const fn boxed(tag: u8, payload: u64) -> u64 {
82    CANON_NAN | ((tag as u64) << TAG_SHIFT) | payload
83}
84
85// -- Heap slot identity (grounded in Value.lean `SlotId`) --------------------
86
87/// A validated heap-reference payload: `segment:u16 << 32 | slot:u32`, both
88/// nonzero. Illegal (zero) identities are unrepresentable.
89#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
90pub struct SlotId {
91    segment: NonZeroU16,
92    slot: NonZeroU32,
93}
94
95impl SlotId {
96    /// The total constructor over already-validated nonzero fields.
97    #[inline]
98    pub const fn new(segment: NonZeroU16, slot: NonZeroU32) -> Self {
99        Self { segment, slot }
100    }
101
102    /// Parses raw parts, rejecting a zero segment or zero slot (`unpackSlot`).
103    #[inline]
104    pub const fn from_parts(segment: u16, slot: u32) -> Option<Self> {
105        match (NonZeroU16::new(segment), NonZeroU32::new(slot)) {
106            (Some(segment), Some(slot)) => Some(Self { segment, slot }),
107            _ => None,
108        }
109    }
110
111    /// The nonzero segment half.
112    #[inline]
113    pub const fn segment(self) -> u16 {
114        self.segment.get()
115    }
116
117    /// The nonzero slot half.
118    #[inline]
119    pub const fn slot(self) -> u32 {
120        self.slot.get()
121    }
122
123    /// The 48-bit packed payload, `packSlot`.
124    #[inline]
125    const fn payload(self) -> u64 {
126        ((self.segment.get() as u64) << 32) | self.slot.get() as u64
127    }
128}
129
130// -- Value (grounded in Value.lean `Value`/`encode`/`decode`) ----------------
131
132/// A NaN-boxed JavaScript value. ABI-identical to a `u64`, so `*mut Value`
133/// arrays and `Completion` fields carry it with no wrapping.
134#[repr(transparent)]
135#[derive(Clone, Copy, PartialEq, Eq, Hash)]
136pub struct Value(u64);
137
138/// The decoded meaning of a `Value`, mirroring the `Value` inductive.
139#[derive(Clone, Copy, PartialEq, Debug)]
140pub enum Decoded {
141    /// A non-boxed IEEE-754 double.
142    Number(f64),
143    /// A validated heap reference.
144    HeapRef(SlotId),
145    /// A boxed 32-bit integer.
146    Int32(u32),
147    /// `undefined`.
148    Undefined,
149    /// `null`.
150    Null,
151    /// A boolean.
152    Boolean(bool),
153    /// The array/TDZ hole.
154    Hole,
155    /// An uninitialized register slot.
156    Uninitialized,
157}
158
159impl Value {
160    /// The canonical positive quiet NaN, `0x7ff8_0000_0000_0000`.
161    pub const CANON_NAN: u64 = CANON_NAN;
162
163    /// `undefined`.
164    pub const UNDEFINED: Value = Value(boxed(TAG_UNDEFINED, 0));
165    /// `null`.
166    pub const NULL: Value = Value(boxed(TAG_NULL, 0));
167    /// The array/TDZ hole.
168    pub const HOLE: Value = Value(boxed(TAG_HOLE, 0));
169    /// The uninitialized register-slot sentinel written by the frame prologue.
170    pub const UNINITIALIZED: Value = Value(boxed(TAG_UNINITIALIZED, 0));
171    /// `false`.
172    pub const FALSE: Value = Value(boxed(TAG_BOOLEAN, 0));
173    /// `true`.
174    pub const TRUE: Value = Value(boxed(TAG_BOOLEAN, 1));
175
176    /// Boxes a boolean (`boolPayload`).
177    #[inline]
178    pub const fn boolean(value: bool) -> Value {
179        Value(boxed(TAG_BOOLEAN, value as u64))
180    }
181
182    /// Boxes a 32-bit integer (`int32Payload`, upper half zero).
183    #[inline]
184    pub const fn int32(value: u32) -> Value {
185        Value(boxed(TAG_INT32, value as u64))
186    }
187
188    /// Boxes a validated heap reference (`packSlot`).
189    #[inline]
190    pub const fn heap_ref(id: SlotId) -> Value {
191        Value(boxed(TAG_HEAP_REF, id.payload()))
192    }
193
194    /// Encodes a double. Every NaN is canonicalized to `CANON_NAN`, so the
195    /// result never collides with the boxed range (`canonicalizeNaN`).
196    #[inline]
197    pub fn number(value: f64) -> Value {
198        if value.is_nan() {
199            Value(CANON_NAN)
200        } else {
201            Value(value.to_bits())
202        }
203    }
204
205    /// Reinterprets a raw 64-bit ABI word as a `Value`. The wire word is taken
206    /// verbatim; use [`Value::decode`] to interpret it.
207    #[inline]
208    pub const fn from_bits(bits: u64) -> Value {
209        Value(bits)
210    }
211
212    /// The raw 64-bit ABI word.
213    #[inline]
214    pub const fn to_bits(self) -> u64 {
215        self.0
216    }
217
218    /// Whether the word is a non-boxed arithmetic double.
219    #[inline]
220    pub const fn is_number(self) -> bool {
221        !is_boxed(self.0)
222    }
223
224    /// Whether the word is exactly the uninitialized sentinel. This is the
225    /// hot check the frame prologue and GC scan rely on.
226    #[inline]
227    pub const fn is_uninitialized(self) -> bool {
228        self.0 == Value::UNINITIALIZED.0
229    }
230
231    /// Interprets the word per the tag-specific payload rules (`decode`).
232    /// Returns `None` for a boxed word whose payload is malformed for its tag.
233    pub const fn decode(self) -> Option<Decoded> {
234        let bits = self.0;
235        if !is_boxed(bits) {
236            return Some(Decoded::Number(f64::from_bits(bits)));
237        }
238        let payload = bits & PAYLOAD_MASK;
239        let upper = (payload >> 32) as u16;
240        let lower = payload as u32;
241        match tag_of(bits) as u8 {
242            TAG_HEAP_REF => match SlotId::from_parts(upper, lower) {
243                Some(id) => Some(Decoded::HeapRef(id)),
244                None => None,
245            },
246            TAG_INT32 => {
247                if upper == 0 {
248                    Some(Decoded::Int32(lower))
249                } else {
250                    None
251                }
252            }
253            TAG_UNDEFINED => {
254                if payload == 0 {
255                    Some(Decoded::Undefined)
256                } else {
257                    None
258                }
259            }
260            TAG_NULL => {
261                if payload == 0 {
262                    Some(Decoded::Null)
263                } else {
264                    None
265                }
266            }
267            TAG_BOOLEAN => match payload {
268                0 => Some(Decoded::Boolean(false)),
269                1 => Some(Decoded::Boolean(true)),
270                _ => None,
271            },
272            TAG_HOLE => {
273                if payload == 0 {
274                    Some(Decoded::Hole)
275                } else {
276                    None
277                }
278            }
279            TAG_UNINITIALIZED => {
280                if payload == 0 {
281                    Some(Decoded::Uninitialized)
282                } else {
283                    None
284                }
285            }
286            _ => None,
287        }
288    }
289
290    /// The double value, when the word is a non-boxed number.
291    #[inline]
292    pub fn as_f64(self) -> Option<f64> {
293        match self.decode() {
294            Some(Decoded::Number(value)) => Some(value),
295            _ => None,
296        }
297    }
298
299    /// The integer, when the word is a well-formed boxed `int32`.
300    #[inline]
301    pub const fn as_int32(self) -> Option<u32> {
302        if is_boxed(self.0) && tag_of(self.0) as u8 == TAG_INT32 && (self.0 & UPPER_MASK) == 0 {
303            Some(self.0 as u32)
304        } else {
305            None
306        }
307    }
308
309    /// The boolean, when the word is a well-formed boxed boolean.
310    #[inline]
311    pub const fn as_bool(self) -> Option<bool> {
312        if is_boxed(self.0) && tag_of(self.0) as u8 == TAG_BOOLEAN {
313            match self.0 & PAYLOAD_MASK {
314                0 => Some(false),
315                1 => Some(true),
316                _ => None,
317            }
318        } else {
319            None
320        }
321    }
322
323    /// The heap slot, when the word is a well-formed boxed reference.
324    #[inline]
325    pub const fn as_heap_ref(self) -> Option<SlotId> {
326        if is_boxed(self.0) && tag_of(self.0) as u8 == TAG_HEAP_REF {
327            let payload = self.0 & PAYLOAD_MASK;
328            SlotId::from_parts((payload >> 32) as u16, payload as u32)
329        } else {
330            None
331        }
332    }
333}
334
335impl core::fmt::Debug for Value {
336    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
337        match self.decode() {
338            Some(decoded) => write!(f, "Value({decoded:?})"),
339            None => write!(f, "Value(malformed {:#018x})", self.0),
340        }
341    }
342}
343
344// -- Entry ABI (grounded in the canonical plan N5) ---------------------------
345
346/// The completion class returned by a native entry, as the raw `u32` result.
347#[repr(u32)]
348#[derive(Clone, Copy, PartialEq, Eq, Debug)]
349pub enum CompletionTag {
350    /// `out.value` is the return value.
351    Normal = 0,
352    /// `out.value` is a rooted error handle.
353    Throw = 1,
354    /// A resume offset was written to the frame; `out.value` is the yield.
355    Suspend = 2,
356    /// `out.value` encodes a `TrapRecordId`; control leaves to the runtime.
357    FatalTrap = 3,
358}
359
360impl CompletionTag {
361    /// The raw ABI discriminant.
362    #[inline]
363    pub const fn as_u32(self) -> u32 {
364        self as u32
365    }
366
367    /// Parses a raw ABI discriminant, rejecting values outside `0..=3`.
368    #[inline]
369    pub const fn from_u32(code: u32) -> Option<CompletionTag> {
370        match code {
371            0 => Some(CompletionTag::Normal),
372            1 => Some(CompletionTag::Throw),
373            2 => Some(CompletionTag::Suspend),
374            3 => Some(CompletionTag::FatalTrap),
375            _ => None,
376        }
377    }
378}
379
380/// The out-parameter written by a native entry. `size = 8`, `align = 8`.
381#[repr(C)]
382#[derive(Clone, Copy, PartialEq, Eq, Debug)]
383pub struct Completion {
384    /// The completion value; its meaning is set by the returned [`CompletionTag`].
385    pub value: Value,
386}
387
388impl Completion {
389    /// Builds a completion carrying `value`.
390    #[inline]
391    pub const fn new(value: Value) -> Completion {
392        Completion { value }
393    }
394}
395
396// -- ShadowFrame (grounded in Abi.lean `ShadowFrame`/`shadowFrameBytes`) ------
397
398/// The register frame header shared by the interpreter and native code.
399///
400/// Layout is fixed and identical on every 64-bit target: `previous` at 0,
401/// `bytecode_pc` at 8, `module_id` at 12, `handles` at 16, and `handle_len` at
402/// 24, with explicit zeroed trailing padding. `size = 32`, `align = 8`.
403/// `handles` addresses exactly `handle_len` `Value`s.
404#[repr(C)]
405#[derive(Clone, Copy, Debug)]
406pub struct ShadowFrame {
407    /// The caller's frame, or null at the base of the stack.
408    pub previous: *mut ShadowFrame,
409    /// The current bytecode program counter.
410    pub bytecode_pc: u32,
411    /// The dense module id of the executing function.
412    pub module_id: u32,
413    /// The register array; register `r[i]` is `handles[i]`.
414    pub handles: *mut Value,
415    /// The number of live registers, equal to `function.register_count`.
416    pub handle_len: u16,
417    _pad1: [u8; 6],
418}
419
420impl ShadowFrame {
421    /// Builds a frame header with physically zeroed padding.
422    #[inline]
423    pub fn new(
424        previous: *mut ShadowFrame,
425        bytecode_pc: u32,
426        module_id: u32,
427        handles: *mut Value,
428        handle_len: u16,
429    ) -> ShadowFrame {
430        ShadowFrame {
431            previous,
432            bytecode_pc,
433            module_id,
434            handles,
435            handle_len,
436            _pad1: [0; 6],
437        }
438    }
439}
440
441// -- Compile-time layout assertions ------------------------------------------
442
443const _: () = {
444    use core::mem::{align_of, offset_of, size_of};
445
446    // Value is ABI-identical to u64.
447    assert!(size_of::<Value>() == 8);
448    assert!(align_of::<Value>() == 8);
449
450    // CANON_NAN equals the Lean canonicalHeader (4095) placed at bit 51.
451    assert!(Value::CANON_NAN == 0x7ff8_0000_0000_0000);
452    assert!(Value::CANON_NAN == 4095u64 << 51);
453
454    // Completion: repr(C) { value: Value }, size 8, align 8, value at 0.
455    assert!(size_of::<Completion>() == 8);
456    assert!(align_of::<Completion>() == 8);
457    assert!(offset_of!(Completion, value) == 0);
458
459    // CompletionTag: repr(u32).
460    assert!(size_of::<CompletionTag>() == 4);
461    assert!(align_of::<CompletionTag>() == 4);
462
463    // ShadowFrame: 32 bytes, 8-aligned, fields at 0/8/12/16/24 (Abi.lean).
464    assert!(size_of::<ShadowFrame>() == 32);
465    assert!(align_of::<ShadowFrame>() == 8);
466    assert!(offset_of!(ShadowFrame, previous) == 0);
467    assert!(offset_of!(ShadowFrame, bytecode_pc) == 8);
468    assert!(offset_of!(ShadowFrame, module_id) == 12);
469    assert!(offset_of!(ShadowFrame, handles) == 16);
470    assert!(offset_of!(ShadowFrame, handle_len) == 24);
471};
472
473// -- Native runtime bridge ---------------------------------------------------
474
475pub mod native_bridge;
476pub use native_bridge::*;
477
478#[cfg(test)]
479mod tests {
480    use super::*;
481
482    fn slot(segment: u16, slot: u32) -> SlotId {
483        SlotId::from_parts(segment, slot).expect("nonzero parts")
484    }
485
486    #[test]
487    fn canonical_singleton_bits_match_lean_layout() {
488        // CANON_NAN | (tag << 48), grounded in tagCode and canonicalHeader.
489        assert_eq!(Value::UNDEFINED.to_bits(), 0x7ffb_0000_0000_0000);
490        assert_eq!(Value::NULL.to_bits(), 0x7ffc_0000_0000_0000);
491        assert_eq!(Value::FALSE.to_bits(), 0x7ffd_0000_0000_0000);
492        assert_eq!(Value::TRUE.to_bits(), 0x7ffd_0000_0000_0001);
493        assert_eq!(Value::HOLE.to_bits(), 0x7ffe_0000_0000_0000);
494        assert_eq!(Value::UNINITIALIZED.to_bits(), 0x7fff_0000_0000_0000);
495        assert_eq!(Value::int32(0).to_bits(), 0x7ffa_0000_0000_0000);
496        assert_eq!(Value::heap_ref(slot(1, 1)).to_bits(), 0x7ff9_0001_0000_0001);
497    }
498
499    #[test]
500    fn decode_is_left_inverse_of_encode() {
501        let cases = [
502            (Value::UNDEFINED, Decoded::Undefined),
503            (Value::NULL, Decoded::Null),
504            (Value::HOLE, Decoded::Hole),
505            (Value::UNINITIALIZED, Decoded::Uninitialized),
506            (Value::boolean(false), Decoded::Boolean(false)),
507            (Value::boolean(true), Decoded::Boolean(true)),
508            (Value::int32(0), Decoded::Int32(0)),
509            (Value::int32(u32::MAX), Decoded::Int32(u32::MAX)),
510            (Value::int32(0x1234_5678), Decoded::Int32(0x1234_5678)),
511            (Value::heap_ref(slot(1, 1)), Decoded::HeapRef(slot(1, 1))),
512            (
513                Value::heap_ref(slot(u16::MAX, u32::MAX)),
514                Decoded::HeapRef(slot(u16::MAX, u32::MAX)),
515            ),
516        ];
517        for (value, expected) in cases {
518            assert_eq!(value.decode(), Some(expected), "{value:?}");
519            // Re-encoding the decoded meaning reproduces the exact bits.
520            let reencoded = match expected {
521                Decoded::Undefined => Value::UNDEFINED,
522                Decoded::Null => Value::NULL,
523                Decoded::Hole => Value::HOLE,
524                Decoded::Uninitialized => Value::UNINITIALIZED,
525                Decoded::Boolean(b) => Value::boolean(b),
526                Decoded::Int32(v) => Value::int32(v),
527                Decoded::HeapRef(id) => Value::heap_ref(id),
528                Decoded::Number(x) => Value::number(x),
529            };
530            assert_eq!(reencoded.to_bits(), value.to_bits());
531        }
532    }
533
534    #[test]
535    fn numbers_are_not_boxed_and_roundtrip() {
536        for x in [
537            0.0f64,
538            -0.0,
539            1.5,
540            -2.25,
541            f64::MAX,
542            f64::MIN,
543            f64::INFINITY,
544            f64::NEG_INFINITY,
545        ] {
546            let value = Value::number(x);
547            assert!(value.is_number(), "{x} should be an unboxed number");
548            assert_eq!(value.decode(), Some(Decoded::Number(x)));
549            assert_eq!(value.as_f64(), Some(x));
550            assert_eq!(value.to_bits(), x.to_bits());
551        }
552    }
553
554    #[test]
555    fn every_nan_canonicalizes_and_stays_a_number() {
556        for raw in [
557            f64::NAN.to_bits(),
558            0x7ff8_0000_0000_0001,
559            0xffff_ffff_ffff_ffff,
560            0x7ff0_0000_0000_0001, // signaling NaN
561        ] {
562            let value = Value::number(f64::from_bits(raw));
563            assert_eq!(value.to_bits(), Value::CANON_NAN);
564            assert!(value.is_number());
565            match value.decode() {
566                Some(Decoded::Number(x)) => assert!(x.is_nan()),
567                other => panic!("expected NaN number, got {other:?}"),
568            }
569        }
570    }
571
572    #[test]
573    fn canonical_nan_word_decodes_as_number_not_boxed() {
574        // tag == 0 keeps CANON_NAN out of the boxed range.
575        let value = Value::from_bits(Value::CANON_NAN);
576        assert!(value.is_number());
577        assert!(matches!(value.decode(), Some(Decoded::Number(_))));
578    }
579
580    #[test]
581    fn malformed_boxed_payloads_are_rejected() {
582        // int32 with a nonzero upper half.
583        assert_eq!(
584            Value::from_bits(boxed(TAG_INT32, 0x0001_0000_0000)).decode(),
585            None
586        );
587        // undefined / null / hole / uninitialized with a nonzero payload.
588        assert_eq!(Value::from_bits(boxed(TAG_UNDEFINED, 1)).decode(), None);
589        assert_eq!(Value::from_bits(boxed(TAG_NULL, 1)).decode(), None);
590        assert_eq!(Value::from_bits(boxed(TAG_HOLE, 1)).decode(), None);
591        assert_eq!(Value::from_bits(boxed(TAG_UNINITIALIZED, 1)).decode(), None);
592        // boolean payload outside {0, 1}.
593        assert_eq!(Value::from_bits(boxed(TAG_BOOLEAN, 2)).decode(), None);
594        // heapRef with a zero segment or zero slot.
595        assert_eq!(
596            Value::from_bits(boxed(TAG_HEAP_REF, 0x0000_0000_0001)).decode(),
597            None
598        );
599        assert_eq!(
600            Value::from_bits(boxed(TAG_HEAP_REF, 0x0001_0000_0000)).decode(),
601            None
602        );
603    }
604
605    #[test]
606    fn distinct_tags_never_share_an_encoding() {
607        // tags_disjoint: the same payload under different tags yields different words.
608        let payload = 0u64;
609        let tags = [
610            TAG_HEAP_REF,
611            TAG_INT32,
612            TAG_UNDEFINED,
613            TAG_NULL,
614            TAG_BOOLEAN,
615            TAG_HOLE,
616            TAG_UNINITIALIZED,
617        ];
618        for (i, &left) in tags.iter().enumerate() {
619            for &right in &tags[i + 1..] {
620                assert_ne!(boxed(left, payload), boxed(right, payload));
621            }
622        }
623    }
624
625    #[test]
626    fn slot_id_rejects_zero_parts() {
627        assert!(SlotId::from_parts(0, 1).is_none());
628        assert!(SlotId::from_parts(1, 0).is_none());
629        assert!(SlotId::from_parts(0, 0).is_none());
630        let id = slot(7, 9);
631        assert_eq!(id.segment(), 7);
632        assert_eq!(id.slot(), 9);
633    }
634
635    #[test]
636    fn typed_accessors_agree_with_decode() {
637        assert_eq!(Value::int32(42).as_int32(), Some(42));
638        assert_eq!(Value::UNDEFINED.as_int32(), None);
639        assert_eq!(Value::boolean(true).as_bool(), Some(true));
640        assert_eq!(Value::boolean(false).as_bool(), Some(false));
641        assert_eq!(Value::int32(1).as_bool(), None);
642        let id = slot(3, 4);
643        assert_eq!(Value::heap_ref(id).as_heap_ref(), Some(id));
644        assert_eq!(Value::NULL.as_heap_ref(), None);
645        assert!(Value::UNINITIALIZED.is_uninitialized());
646        assert!(!Value::HOLE.is_uninitialized());
647    }
648
649    #[test]
650    fn completion_tag_roundtrips_and_rejects_out_of_range() {
651        for tag in [
652            CompletionTag::Normal,
653            CompletionTag::Throw,
654            CompletionTag::Suspend,
655            CompletionTag::FatalTrap,
656        ] {
657            assert_eq!(CompletionTag::from_u32(tag.as_u32()), Some(tag));
658        }
659        assert_eq!(CompletionTag::Normal.as_u32(), 0);
660        assert_eq!(CompletionTag::FatalTrap.as_u32(), 3);
661        assert_eq!(CompletionTag::from_u32(4), None);
662        assert_eq!(CompletionTag::from_u32(u32::MAX), None);
663    }
664
665    #[test]
666    fn shadow_frame_new_zeroes_padding_and_keeps_fields() {
667        let mut register = Value::UNINITIALIZED;
668        let frame = ShadowFrame::new(core::ptr::null_mut(), 12, 7, &mut register, 1);
669        assert!(frame.previous.is_null());
670        assert_eq!(frame.bytecode_pc, 12);
671        assert_eq!(frame.module_id, 7);
672        assert_eq!(frame.handle_len, 1);
673        assert_eq!(frame._pad1, [0; 6]);
674        assert!(core::ptr::eq(frame.handles, &raw mut register));
675    }
676
677    #[test]
678    fn completion_wraps_value() {
679        let completion = Completion::new(Value::int32(5));
680        assert_eq!(completion.value.as_int32(), Some(5));
681    }
682}