Skip to main content

bamts_native/
native_bridge.rs

1//! The native runtime bridge: the typed helper-call algebra, the exact 32
2//! `bamts_*` C-ABI helper exports, the panic- and nesting-safe thread-local
3//! [`NativeOps`] dispatch seam, and the feature-gated JIT and AOT linkage
4//! surfaces.
5//!
6//! # Where the unsafe lives
7//!
8//! Generated CLIF (JIT) and linked object code (AOT) own control flow but call
9//! back into this crate for every value/heap/host operation. This module is the
10//! *only* place raw pointers from that generated code are turned into safe Rust:
11//!
12//! * The exported `bamts_*` wrappers ([`bamts_load_constant`] …) receive raw
13//!   `*mut ShadowFrame` / `*mut Completion` from CLIF, validate them into a safe
14//!   [`NativeFrame`], and dispatch to the current [`NativeOps`]. They never
15//!   unwind across the C boundary: any panic, missing dispatcher, or invalid
16//!   frame is turned into a [`CompletionTag::FatalTrap`].
17//! * [`JitEntry`] (feature `jit-entry`) wraps a finalized `cranelift-jit`
18//!   entry-point pointer, bound to its `JITModule` lifetime.
19//! * [`linked_program`] (feature `aot-image`) reads the generated external
20//!   `bamts_program_descriptor` image.
21//!
22//! The helper table below (variant order, symbols, and operand types) is the
23//! export contract shared verbatim with `bamts_codegen::Helper`
24//! (`crates/bamts-codegen/src/lib.rs`, `enum Helper` / `symbol` /
25//! `external_index` / `param_types`). Any drift breaks linkage, so a
26//! self-consistent parity test pins it (`bamts-codegen` depends on this crate
27//! under `host-jit`, so a direct comparison would be a dependency cycle).
28
29use core::mem::{align_of, size_of};
30
31use std::cell::Cell;
32use std::fmt;
33use std::panic::{AssertUnwindSafe, catch_unwind};
34
35use crate::{Completion, CompletionTag, ShadowFrame, Value};
36
37// -- The helper algebra ------------------------------------------------------
38
39/// The number of runtime helpers, `0..HELPER_COUNT`.
40pub const HELPER_COUNT: u32 = 32;
41
42/// A runtime helper, identified by its stable ABI index. The variant order is
43/// the canonical `external_index` order (0..31) and is byte-identical to
44/// `bamts_codegen::Helper`; [`NativeHelper::symbol`] returns the exact linker
45/// symbol generated code resolves against.
46#[repr(u32)]
47#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
48pub enum NativeHelper {
49    /// # Safety
50    ///
51    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
52    /// range is disjoint from its header, and a live, aligned, writable `out` when
53    /// this helper has one. Both remain valid and unaliased for the full call.
54    ///
55    /// `bamts_load_constant` — index 0.
56    LoadConstant = 0,
57    /// # Safety
58    ///
59    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
60    /// range is disjoint from its header, and a live, aligned, writable `out` when
61    /// this helper has one. Both remain valid and unaliased for the full call.
62    ///
63    /// `bamts_unary` — index 1.
64    Unary = 1,
65    /// # Safety
66    ///
67    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
68    /// range is disjoint from its header, and a live, aligned, writable `out` when
69    /// this helper has one. Both remain valid and unaliased for the full call.
70    ///
71    /// `bamts_binary` — index 2.
72    Binary = 2,
73    /// # Safety
74    ///
75    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
76    /// range is disjoint from its header, and a live, aligned, writable `out` when
77    /// this helper has one. Both remain valid and unaliased for the full call.
78    ///
79    /// `bamts_create_object` — index 3.
80    CreateObject = 3,
81    /// # Safety
82    ///
83    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
84    /// range is disjoint from its header, and a live, aligned, writable `out` when
85    /// this helper has one. Both remain valid and unaliased for the full call.
86    ///
87    /// `bamts_create_array` — index 4.
88    CreateArray = 4,
89    /// # Safety
90    ///
91    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
92    /// range is disjoint from its header, and a live, aligned, writable `out` when
93    /// this helper has one. Both remain valid and unaliased for the full call.
94    ///
95    /// `bamts_create_closure` — index 5.
96    CreateClosure = 5,
97    /// # Safety
98    ///
99    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
100    /// range is disjoint from its header, and a live, aligned, writable `out` when
101    /// this helper has one. Both remain valid and unaliased for the full call.
102    ///
103    /// `bamts_get_property` — index 6.
104    GetProperty = 6,
105    /// # Safety
106    ///
107    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
108    /// range is disjoint from its header, and a live, aligned, writable `out` when
109    /// this helper has one. Both remain valid and unaliased for the full call.
110    ///
111    /// `bamts_set_property` — index 7.
112    SetProperty = 7,
113    /// # Safety
114    ///
115    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
116    /// range is disjoint from its header, and a live, aligned, writable `out` when
117    /// this helper has one. Both remain valid and unaliased for the full call.
118    ///
119    /// `bamts_delete_property` — index 8.
120    DeleteProperty = 8,
121    /// # Safety
122    ///
123    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
124    /// range is disjoint from its header, and a live, aligned, writable `out` when
125    /// this helper has one. Both remain valid and unaliased for the full call.
126    ///
127    /// `bamts_call` — index 9.
128    Call = 9,
129    /// # Safety
130    ///
131    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
132    /// range is disjoint from its header, and a live, aligned, writable `out` when
133    /// this helper has one. Both remain valid and unaliased for the full call.
134    ///
135    /// `bamts_construct` — index 10.
136    Construct = 10,
137    /// # Safety
138    ///
139    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
140    /// range is disjoint from its header, and a live, aligned, writable `out` when
141    /// this helper has one. Both remain valid and unaliased for the full call.
142    ///
143    /// `bamts_import` — index 11.
144    Import = 11,
145    /// # Safety
146    ///
147    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
148    /// range is disjoint from its header, and a live, aligned, writable `out` when
149    /// this helper has one. Both remain valid and unaliased for the full call.
150    ///
151    /// `bamts_truthy` — index 12 (returns `0`/`1`, never writes `out`).
152    Truthy = 12,
153    /// # Safety
154    ///
155    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
156    /// range is disjoint from its header, and a live, aligned, writable `out` when
157    /// this helper has one. Both remain valid and unaliased for the full call.
158    ///
159    /// `bamts_resume_value` — index 13.
160    ResumeValue = 13,
161    /// # Safety
162    ///
163    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
164    /// range is disjoint from its header, and a live, aligned, writable `out` when
165    /// this helper has one. Both remain valid and unaliased for the full call.
166    ///
167    /// `bamts_define_accessor` — index 14.
168    DefineAccessor = 14,
169    /// # Safety
170    ///
171    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
172    /// range is disjoint from its header, and a live, aligned, writable `out` when
173    /// this helper has one. Both remain valid and unaliased for the full call.
174    ///
175    /// `bamts_load_global` — index 15.
176    LoadGlobal = 15,
177    /// # Safety
178    ///
179    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
180    /// range is disjoint from its header, and a live, aligned, writable `out` when
181    /// this helper has one. Both remain valid and unaliased for the full call.
182    ///
183    /// `bamts_store_global` — index 16.
184    StoreGlobal = 16,
185    /// # Safety
186    ///
187    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
188    /// range is disjoint from its header, and a live, aligned, writable `out` when
189    /// this helper has one. Both remain valid and unaliased for the full call.
190    ///
191    /// `bamts_typeof_global` — index 17.
192    TypeOfGlobal = 17,
193    /// # Safety
194    ///
195    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
196    /// range is disjoint from its header, and a live, aligned, writable `out` when
197    /// this helper has one. Both remain valid and unaliased for the full call.
198    ///
199    /// `bamts_load_this` — index 18.
200    LoadThis = 18,
201    /// # Safety
202    ///
203    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
204    /// range is disjoint from its header, and a live, aligned, writable `out` when
205    /// this helper has one. Both remain valid and unaliased for the full call.
206    ///
207    /// `bamts_load_arguments` — index 19.
208    LoadArguments = 19,
209    /// # Safety
210    ///
211    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
212    /// range is disjoint from its header, and a live, aligned, writable `out` when
213    /// this helper has one. Both remain valid and unaliased for the full call.
214    ///
215    /// `bamts_load_new_target` — index 20.
216    LoadNewTarget = 20,
217    /// # Safety
218    ///
219    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
220    /// range is disjoint from its header, and a live, aligned, writable `out` when
221    /// this helper has one. Both remain valid and unaliased for the full call.
222    ///
223    /// `bamts_array_push` — index 21.
224    ArrayPush = 21,
225    /// # Safety
226    ///
227    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
228    /// range is disjoint from its header, and a live, aligned, writable `out` when
229    /// this helper has one. Both remain valid and unaliased for the full call.
230    ///
231    /// `bamts_array_extend` — index 22.
232    ArrayExtend = 22,
233    /// # Safety
234    ///
235    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
236    /// range is disjoint from its header, and a live, aligned, writable `out` when
237    /// this helper has one. Both remain valid and unaliased for the full call.
238    ///
239    /// `bamts_object_spread` — index 23.
240    ObjectSpread = 23,
241    /// # Safety
242    ///
243    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
244    /// range is disjoint from its header, and a live, aligned, writable `out` when
245    /// this helper has one. Both remain valid and unaliased for the full call.
246    ///
247    /// `bamts_set_prototype` — index 24.
248    SetPrototype = 24,
249    /// # Safety
250    ///
251    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
252    /// range is disjoint from its header, and a live, aligned, writable `out` when
253    /// this helper has one. Both remain valid and unaliased for the full call.
254    ///
255    /// `bamts_create_private_name` — index 25.
256    CreatePrivateName = 25,
257    /// # Safety
258    ///
259    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
260    /// range is disjoint from its header, and a live, aligned, writable `out` when
261    /// this helper has one. Both remain valid and unaliased for the full call.
262    ///
263    /// `bamts_create_regexp` — index 26.
264    CreateRegExp = 26,
265    /// # Safety
266    ///
267    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
268    /// range is disjoint from its header, and a live, aligned, writable `out` when
269    /// this helper has one. Both remain valid and unaliased for the full call.
270    ///
271    /// `bamts_get_iterator` — index 27.
272    GetIterator = 27,
273    /// # Safety
274    ///
275    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
276    /// range is disjoint from its header, and a live, aligned, writable `out` when
277    /// this helper has one. Both remain valid and unaliased for the full call.
278    ///
279    /// `bamts_iterator_next` — index 28 (writes two registers directly).
280    IteratorNext = 28,
281    /// # Safety
282    ///
283    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
284    /// range is disjoint from its header, and a live, aligned, writable `out` when
285    /// this helper has one. Both remain valid and unaliased for the full call.
286    ///
287    /// `bamts_export` — index 29.
288    Export = 29,
289    /// # Safety
290    ///
291    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
292    /// range is disjoint from its header, and a live, aligned, writable `out`.
293    /// Both remain valid and unaliased for the full call.
294    ///
295    /// `bamts_consume_fuel` — index 30.
296    ConsumeFuel = 30,
297    /// `bamts_create_cell` — index 31.
298    ///
299    /// # Safety
300    ///
301    /// The caller must provide a live, uniquely owned `frame` whose nonempty
302    /// handle range is disjoint from its header, and a live, aligned, writable
303    /// `out`. Both remain valid and unaliased for the full call.
304    CreateCell = 31,
305}
306
307impl NativeHelper {
308    /// The C symbol generated code links against. Byte-identical to
309    /// # Safety
310    ///
311    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
312    /// range is disjoint from its header, and a live, aligned, writable `out` when
313    /// this helper has one. Both remain valid and unaliased for the full call.
314    ///
315    /// `bamts_codegen::Helper::symbol`.
316    #[must_use]
317    pub const fn symbol(self) -> &'static str {
318        match self {
319            NativeHelper::LoadConstant => "bamts_load_constant",
320            NativeHelper::Unary => "bamts_unary",
321            NativeHelper::Binary => "bamts_binary",
322            NativeHelper::CreateObject => "bamts_create_object",
323            NativeHelper::CreateArray => "bamts_create_array",
324            NativeHelper::CreateClosure => "bamts_create_closure",
325            NativeHelper::GetProperty => "bamts_get_property",
326            NativeHelper::SetProperty => "bamts_set_property",
327            NativeHelper::DeleteProperty => "bamts_delete_property",
328            NativeHelper::Call => "bamts_call",
329            NativeHelper::Construct => "bamts_construct",
330            NativeHelper::Import => "bamts_import",
331            NativeHelper::Truthy => "bamts_truthy",
332            NativeHelper::ResumeValue => "bamts_resume_value",
333            NativeHelper::DefineAccessor => "bamts_define_accessor",
334            NativeHelper::LoadGlobal => "bamts_load_global",
335            NativeHelper::StoreGlobal => "bamts_store_global",
336            NativeHelper::TypeOfGlobal => "bamts_typeof_global",
337            NativeHelper::LoadThis => "bamts_load_this",
338            NativeHelper::LoadArguments => "bamts_load_arguments",
339            NativeHelper::LoadNewTarget => "bamts_load_new_target",
340            NativeHelper::ArrayPush => "bamts_array_push",
341            NativeHelper::ArrayExtend => "bamts_array_extend",
342            NativeHelper::ObjectSpread => "bamts_object_spread",
343            NativeHelper::SetPrototype => "bamts_set_prototype",
344            NativeHelper::CreatePrivateName => "bamts_create_private_name",
345            NativeHelper::CreateRegExp => "bamts_create_regexp",
346            NativeHelper::GetIterator => "bamts_get_iterator",
347            NativeHelper::IteratorNext => "bamts_iterator_next",
348            NativeHelper::Export => "bamts_export",
349            NativeHelper::ConsumeFuel => "bamts_consume_fuel",
350            NativeHelper::CreateCell => "bamts_create_cell",
351        }
352    }
353
354    /// The stable ABI index, `0..HELPER_COUNT`.
355    #[inline]
356    #[must_use]
357    pub const fn as_u32(self) -> u32 {
358        self as u32
359    }
360
361    /// Parses an ABI index, rejecting values outside `0..HELPER_COUNT`.
362    #[must_use]
363    pub const fn from_u32(index: u32) -> Option<NativeHelper> {
364        match index {
365            0 => Some(NativeHelper::LoadConstant),
366            1 => Some(NativeHelper::Unary),
367            2 => Some(NativeHelper::Binary),
368            3 => Some(NativeHelper::CreateObject),
369            4 => Some(NativeHelper::CreateArray),
370            5 => Some(NativeHelper::CreateClosure),
371            6 => Some(NativeHelper::GetProperty),
372            7 => Some(NativeHelper::SetProperty),
373            8 => Some(NativeHelper::DeleteProperty),
374            9 => Some(NativeHelper::Call),
375            10 => Some(NativeHelper::Construct),
376            11 => Some(NativeHelper::Import),
377            12 => Some(NativeHelper::Truthy),
378            13 => Some(NativeHelper::ResumeValue),
379            14 => Some(NativeHelper::DefineAccessor),
380            15 => Some(NativeHelper::LoadGlobal),
381            16 => Some(NativeHelper::StoreGlobal),
382            17 => Some(NativeHelper::TypeOfGlobal),
383            18 => Some(NativeHelper::LoadThis),
384            19 => Some(NativeHelper::LoadArguments),
385            20 => Some(NativeHelper::LoadNewTarget),
386            21 => Some(NativeHelper::ArrayPush),
387            22 => Some(NativeHelper::ArrayExtend),
388            23 => Some(NativeHelper::ObjectSpread),
389            24 => Some(NativeHelper::SetPrototype),
390            25 => Some(NativeHelper::CreatePrivateName),
391            26 => Some(NativeHelper::CreateRegExp),
392            27 => Some(NativeHelper::GetIterator),
393            28 => Some(NativeHelper::IteratorNext),
394            29 => Some(NativeHelper::Export),
395            30 => Some(NativeHelper::ConsumeFuel),
396            31 => Some(NativeHelper::CreateCell),
397            _ => None,
398        }
399    }
400}
401
402/// A typed helper invocation. Runtime `Value`s carry their [`Value`] type;
403/// operator selectors, string-constant ids, function/register indices, and
404/// protocol kinds are the raw ABI `u32` selectors codegen passes (never
405/// # Safety
406///
407/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
408/// range is disjoint from its header, and a live, aligned, writable `out` when
409/// this helper has one. Both remain valid and unaliased for the full call.
410///
411/// `bamts_bytecode` enums — this crate does not depend on the bytecode). The
412/// implicit `frame` and completion `out` are supplied by [`NativeOps::dispatch`]
413/// and the wrapper, not by the operands here.
414#[derive(Clone, Copy, Debug, PartialEq)]
415pub enum HelperCall {
416    /// Materialize module constant `const_id`.
417    LoadConstant { const_id: u32 },
418    /// Apply unary operator selector `op` to `operand`.
419    Unary { op: u32, operand: Value },
420    /// Apply binary operator selector `op` to `left` and `right`.
421    Binary { op: u32, left: Value, right: Value },
422    /// A fresh empty object.
423    CreateObject,
424    /// A fresh empty array.
425    CreateArray,
426    /// A compiler-private array cell seeded with the uninitialized sentinel.
427    CreateCell,
428    /// A closure over `function_id` binding the `captures` array value.
429    CreateClosure { function_id: u32, captures: Value },
430    /// `object[key]`.
431    GetProperty { object: Value, key: Value },
432    /// `object[key] = value`.
433    SetProperty {
434        object: Value,
435        key: Value,
436        value: Value,
437    },
438    /// `delete object[key]`.
439    DeleteProperty { object: Value, key: Value },
440    /// Call `callee` with receiver `this_value` over the `arguments` array.
441    Call {
442        callee: Value,
443        this_value: Value,
444        arguments: Value,
445    },
446    /// Construct with `callee` over the `arguments` array.
447    Construct { callee: Value, arguments: Value },
448    /// Import the module named by string constant `specifier`.
449    Import { specifier: u32 },
450    /// ToBoolean on `value`. Routed to [`NativeOps::truthy`]; present here for a
451    /// complete algebra but never delivered to [`NativeOps::dispatch`] in normal
452    /// operation.
453    Truthy { value: Value },
454    /// The verified resumed value for the current frame.
455    ResumeValue,
456    /// Install a getter/setter (`kind` selector) under `key`.
457    DefineAccessor {
458        object: Value,
459        key: Value,
460        accessor: Value,
461        kind: u32,
462    },
463    /// `globalThis[name]`.
464    LoadGlobal { name: u32 },
465    /// `globalThis[name] = value`.
466    StoreGlobal { name: u32, value: Value },
467    /// `typeof globalThis[name]`.
468    TypeOfGlobal { name: u32 },
469    /// The `this` binding.
470    LoadThis,
471    /// The `arguments` object.
472    LoadArguments,
473    /// `new.target`.
474    LoadNewTarget,
475    /// Append `value` to `array`.
476    ArrayPush { array: Value, value: Value },
477    /// Spread `iterable` onto the end of `array`.
478    ArrayExtend { array: Value, iterable: Value },
479    /// Copy own enumerable properties of `source` onto `target`.
480    ObjectSpread { target: Value, source: Value },
481    /// Set the `[[Prototype]]` of `object`.
482    SetPrototype { object: Value, prototype: Value },
483    /// A fresh private name described by string constant `description`.
484    CreatePrivateName { description: u32 },
485    /// A `RegExp` from string constants `pattern` and `flags`.
486    CreateRegExp { pattern: u32, flags: u32 },
487    /// Acquire an iterator over `src` using protocol `kind`.
488    GetIterator { src: Value, kind: u32 },
489    /// Advance `iterator`, writing the done flag into register `done_reg` and
490    /// the produced value into register `value_reg` (both via the frame). On
491    /// `Throw`, the thrown handle is the result value and neither register is
492    /// written.
493    IteratorNext {
494        iterator: Value,
495        done_reg: u32,
496        value_reg: u32,
497    },
498    /// Export local value `src` under string constant `name`.
499    Export { name: u32, src: Value },
500    /// Consume `amount` units from the shared instruction budget.
501    ConsumeFuel { amount: u32 },
502}
503
504impl HelperCall {
505    /// The helper this call selects.
506    #[must_use]
507    pub const fn helper(&self) -> NativeHelper {
508        match self {
509            HelperCall::LoadConstant { .. } => NativeHelper::LoadConstant,
510            HelperCall::Unary { .. } => NativeHelper::Unary,
511            HelperCall::Binary { .. } => NativeHelper::Binary,
512            HelperCall::CreateObject => NativeHelper::CreateObject,
513            HelperCall::CreateArray => NativeHelper::CreateArray,
514            HelperCall::CreateCell => NativeHelper::CreateCell,
515            HelperCall::CreateClosure { .. } => NativeHelper::CreateClosure,
516            HelperCall::GetProperty { .. } => NativeHelper::GetProperty,
517            HelperCall::SetProperty { .. } => NativeHelper::SetProperty,
518            HelperCall::DeleteProperty { .. } => NativeHelper::DeleteProperty,
519            HelperCall::Call { .. } => NativeHelper::Call,
520            HelperCall::Construct { .. } => NativeHelper::Construct,
521            HelperCall::Import { .. } => NativeHelper::Import,
522            HelperCall::Truthy { .. } => NativeHelper::Truthy,
523            HelperCall::ResumeValue => NativeHelper::ResumeValue,
524            HelperCall::DefineAccessor { .. } => NativeHelper::DefineAccessor,
525            HelperCall::LoadGlobal { .. } => NativeHelper::LoadGlobal,
526            HelperCall::StoreGlobal { .. } => NativeHelper::StoreGlobal,
527            HelperCall::TypeOfGlobal { .. } => NativeHelper::TypeOfGlobal,
528            HelperCall::LoadThis => NativeHelper::LoadThis,
529            HelperCall::LoadArguments => NativeHelper::LoadArguments,
530            HelperCall::LoadNewTarget => NativeHelper::LoadNewTarget,
531            HelperCall::ArrayPush { .. } => NativeHelper::ArrayPush,
532            HelperCall::ArrayExtend { .. } => NativeHelper::ArrayExtend,
533            HelperCall::ObjectSpread { .. } => NativeHelper::ObjectSpread,
534            HelperCall::SetPrototype { .. } => NativeHelper::SetPrototype,
535            HelperCall::CreatePrivateName { .. } => NativeHelper::CreatePrivateName,
536            HelperCall::CreateRegExp { .. } => NativeHelper::CreateRegExp,
537            HelperCall::GetIterator { .. } => NativeHelper::GetIterator,
538            HelperCall::IteratorNext { .. } => NativeHelper::IteratorNext,
539            HelperCall::Export { .. } => NativeHelper::Export,
540            HelperCall::ConsumeFuel { .. } => NativeHelper::ConsumeFuel,
541        }
542    }
543}
544
545/// The outcome of a completion helper: the ABI tag plus the completion value
546/// its tag interprets (return value, thrown/yielded handle, or trap id).
547#[derive(Clone, Copy, Debug, PartialEq, Eq)]
548pub struct HelperResult {
549    /// The completion class.
550    pub tag: CompletionTag,
551    /// The completion value; meaning fixed by `tag`.
552    pub value: Value,
553}
554
555impl HelperResult {
556    /// A `Normal` completion carrying `value`.
557    #[inline]
558    #[must_use]
559    pub const fn normal(value: Value) -> HelperResult {
560        HelperResult {
561            tag: CompletionTag::Normal,
562            value,
563        }
564    }
565
566    /// A `Throw` completion carrying the rooted error handle `value`.
567    #[inline]
568    #[must_use]
569    pub const fn throw(value: Value) -> HelperResult {
570        HelperResult {
571            tag: CompletionTag::Throw,
572            value,
573        }
574    }
575}
576
577// -- Trap record ids ---------------------------------------------------------
578
579/// `out.value` id written when a wrapper runs with no installed [`NativeOps`].
580pub const TRAP_MISSING_NATIVE_OPS: u32 = 0x1000;
581/// `out.value` id written when a wrapper receives an invalid frame pointer.
582pub const TRAP_INVALID_FRAME: u32 = 0x1001;
583/// `out.value` id written when the dispatcher panics (caught at the boundary).
584pub const TRAP_PANIC: u32 = 0x1002;
585/// `out.value` id written when a helper receives an out-of-range register index.
586pub const TRAP_INVALID_REGISTER: u32 = 0x1003;
587/// `out.value` id written when a native entry returns an unrecognized completion tag.
588pub const TRAP_INVALID_COMPLETION_TAG: u32 = 0x1004;
589
590// -- The safe frame view -----------------------------------------------------
591
592/// A validated safe view of a [`ShadowFrame`] and its register (`handle`)
593/// array. Constructed only inside the `bamts_*` wrappers, which reject a null,
594/// misaligned, or malformed frame before dispatching.
595pub struct NativeFrame<'a> {
596    frame: &'a mut ShadowFrame,
597    handles: &'a mut [Value],
598}
599
600impl<'a> NativeFrame<'a> {
601    /// Builds a safe view from an already-borrowed frame and register slice.
602    /// Returns `None` unless the frame's `handle_len` and `handles` metadata
603    /// describe exactly `handles`. This lets runtime-owned execution paths use
604    /// the same checked view without raw pointers or unsafe code.
605    #[must_use]
606    pub fn new(frame: &'a mut ShadowFrame, handles: &'a mut [Value]) -> Option<NativeFrame<'a>> {
607        let len = u16::try_from(handles.len()).ok()?;
608        if frame.handle_len != len {
609            return None;
610        }
611        if !handles.is_empty() && !core::ptr::eq(frame.handles, handles.as_mut_ptr()) {
612            return None;
613        }
614        Some(NativeFrame { frame, handles })
615    }
616
617    /// Validates a raw frame pointer into a safe view.
618    ///
619    /// # Safety
620    ///
621    /// `frame`, when non-null, must point to a live, unaliased [`ShadowFrame`]
622    /// whose `handles` field addresses exactly `handle_len` initialized
623    /// [`Value`]s (or is unused when `handle_len == 0`). Generated native code
624    /// upholds this: it owns the frame for the synchronous duration of the
625    /// helper call, and the register array is a distinct allocation from the
626    /// 32-byte header. Returns `None` for a null or misaligned pointer.
627    #[must_use]
628    pub unsafe fn from_raw(frame: *mut ShadowFrame) -> Option<NativeFrame<'a>> {
629        if frame.is_null() || !frame.addr().is_multiple_of(align_of::<ShadowFrame>()) {
630            return None;
631        }
632        // Read only Copy fields through raw pointers before forming any mutable
633        // reference. This lets us reject a handle range that aliases the header.
634        let len = unsafe { core::ptr::addr_of!((*frame).handle_len).read() as usize };
635        let handles_ptr = unsafe { core::ptr::addr_of!((*frame).handles).read() };
636        if len != 0 {
637            if handles_ptr.is_null() || !handles_ptr.addr().is_multiple_of(align_of::<Value>()) {
638                return None;
639            }
640            let header_start = frame.addr();
641            let header_end = header_start.checked_add(size_of::<ShadowFrame>())?;
642            let handles_start = handles_ptr.addr();
643            let handles_end = handles_start.checked_add(len.checked_mul(size_of::<Value>())?)?;
644            if handles_start < header_end && header_start < handles_end {
645                return None;
646            }
647        }
648        // SAFETY: the caller contract guarantees the validated raw frame is live
649        // and unaliased; the address-range check above proves its handle storage
650        // cannot overlap the header before these mutable references are formed.
651        let header: &'a mut ShadowFrame = unsafe { &mut *frame };
652        let handles: &'a mut [Value] = if len == 0 {
653            &mut []
654        } else {
655            // SAFETY: checked above for alignment, range arithmetic, and
656            // non-overlap; the caller contract guarantees initialized Values.
657            unsafe { core::slice::from_raw_parts_mut(handles_ptr, len) }
658        };
659        Some(NativeFrame {
660            frame: header,
661            handles,
662        })
663    }
664
665    /// The number of live registers (`ShadowFrame::handle_len`).
666    #[inline]
667    #[must_use]
668    pub fn handle_len(&self) -> u32 {
669        u32::from(self.frame.handle_len)
670    }
671
672    /// The dense module id of the executing function.
673    #[inline]
674    #[must_use]
675    pub fn module_id(&self) -> u32 {
676        self.frame.module_id
677    }
678
679    /// The current bytecode program counter / resume token.
680    #[inline]
681    #[must_use]
682    pub fn pc(&self) -> u32 {
683        self.frame.bytecode_pc
684    }
685
686    /// Stores a resume token into the frame (yield path).
687    #[inline]
688    pub fn set_resume(&mut self, token: u32) {
689        self.frame.bytecode_pc = token;
690    }
691
692    /// The register array.
693    #[inline]
694    #[must_use]
695    pub fn registers(&self) -> &[Value] {
696        self.handles
697    }
698
699    /// The register array, mutably.
700    #[inline]
701    #[must_use]
702    pub fn registers_mut(&mut self) -> &mut [Value] {
703        self.handles
704    }
705
706    /// Register `index`. Panics if out of range; the wrapper turns the panic
707    /// into a [`CompletionTag::FatalTrap`]. Use [`NativeFrame::try_register`] to
708    /// branch instead.
709    #[inline]
710    #[must_use]
711    pub fn register(&self, index: u32) -> Value {
712        self.handles[index as usize]
713    }
714
715    /// Sets register `index`. Panics if out of range (see [`NativeFrame::register`]).
716    #[inline]
717    pub fn set_register(&mut self, index: u32, value: Value) {
718        self.handles[index as usize] = value;
719    }
720
721    /// Register `index`, or `None` when out of range.
722    #[inline]
723    #[must_use]
724    pub fn try_register(&self, index: u32) -> Option<Value> {
725        self.handles.get(index as usize).copied()
726    }
727
728    /// Sets register `index`, returning `false` when out of range.
729    #[inline]
730    pub fn try_set_register(&mut self, index: u32, value: Value) -> bool {
731        match self.handles.get_mut(index as usize) {
732            Some(slot) => {
733                *slot = value;
734                true
735            }
736            None => false,
737        }
738    }
739
740    /// The caller's frame, or null at the base of the stack.
741    #[inline]
742    #[must_use]
743    pub fn previous(&self) -> *mut ShadowFrame {
744        self.frame.previous
745    }
746}
747/// Validates a raw frame pointer into a safe view.
748///
749/// # Safety
750///
751/// `frame` must be either null or a live, unaliased [`ShadowFrame`] pointer
752/// owned by the caller for the synchronous duration of the call, with
753/// `handle_len` initialized [`Value`]s when non-null. This is the same
754/// contract as [`NativeFrame::from_raw`].
755unsafe fn frame_from_raw<'a>(frame: *mut ShadowFrame) -> Option<NativeFrame<'a>> {
756    // SAFETY: forwarded to `NativeFrame::from_raw`, which validates null and
757    // alignment before dereferencing; the caller upholds the lifetime and
758    // aliasing contract described above.
759    unsafe { NativeFrame::from_raw(frame) }
760}
761
762// -- The dispatch seam -------------------------------------------------------
763
764/// The runtime semantic engine the exported helpers dispatch into. Implemented
765/// by `bamts_runtime`'s native engine; installed for the current thread with
766/// [`with_native_ops`].
767///
768/// Methods take `&self` because dispatch is **re-entrant**: a `Call`,
769/// `Construct`, or `CreateClosure` may re-enter native code that calls another
770/// helper, which dispatches back into the same instance on the same thread.
771/// Shared `&self` reborrows alias soundly, so the outer `dispatch` may resume
772/// touching `self` after the nested call returns — e.g. to pop an activation
773/// record or record a result. The engine therefore holds its mutable state
774/// behind interior mutability (`Cell`/`RefCell`/`UnsafeCell`); the one
775/// discipline is to never hold a `RefCell` borrow guard across a nested native
776/// re-entry (the re-entrant borrow would panic). Nested execution always uses a
777/// distinct child [`ShadowFrame`], so the outer `frame` borrow never aliases it.
778pub trait NativeOps {
779    /// The total ToBoolean coercion. Never throws.
780    fn truthy(&self, frame: &mut NativeFrame<'_>, value: Value) -> bool;
781
782    /// Executes one completion helper, writing any register side effects through
783    /// `frame` and returning the completion.
784    fn dispatch(&self, frame: &mut NativeFrame<'_>, call: HelperCall) -> HelperResult;
785}
786
787type ErasedOps = *const (dyn NativeOps + 'static);
788
789thread_local! {
790    /// The [`NativeOps`] active on this thread, or `None`. Holds an erased raw
791    /// pointer valid only for the duration of the [`with_native_ops`] scope that
792    /// installed it.
793    static CURRENT_OPS: Cell<Option<ErasedOps>> = const { Cell::new(None) };
794}
795
796/// Installs `ops` as the current thread's dispatcher for the duration of
797/// `body`, then restores the previous dispatcher.
798///
799/// Nesting-safe: an inner `with_native_ops` saves and restores the outer
800/// dispatcher. Panic-safe: the previous dispatcher is restored even if `body`
801/// unwinds (the restore runs in a guard's `Drop`).
802pub fn with_native_ops<R>(ops: &mut dyn NativeOps, body: impl FnOnce() -> R) -> R {
803    // `ops` is taken uniquely to guarantee the caller owns the engine while it
804    // is installed, but the dispatcher is invoked through shared `&self`
805    // reborrows (see `NativeOps`), so it is stored as a shared raw pointer;
806    // re-entrant native calls then form further shared reborrows that alias
807    // soundly.
808    let ptr: *const dyn NativeOps = ops;
809    // SAFETY: `ptr` and `erased` have the identical fat-pointer representation,
810    // alignment, initialization, and provenance; the transmute changes only the
811    // erased lifetime metadata, not the data or vtable addresses. `ptr` stays
812    // valid for all of `body`, and the guard restores the previous slot before
813    // return/unwind, so the erased pointer is never dereferenced outside that
814    // lifetime. No bounds are involved.
815    let erased: ErasedOps = unsafe { core::mem::transmute::<*const dyn NativeOps, ErasedOps>(ptr) };
816    let previous = CURRENT_OPS.with(|slot| slot.replace(Some(erased)));
817    let _guard = OpsGuard { previous };
818    body()
819}
820
821/// Restores the previous [`CURRENT_OPS`] value on scope exit, including unwind.
822struct OpsGuard {
823    previous: Option<ErasedOps>,
824}
825
826impl Drop for OpsGuard {
827    fn drop(&mut self) {
828        CURRENT_OPS.with(|slot| slot.set(self.previous));
829    }
830}
831
832/// Runs `f` with the current thread's dispatcher, or returns `None` if none is
833/// installed.
834fn with_current_ops<R>(f: impl FnOnce(&dyn NativeOps) -> R) -> Option<R> {
835    let ptr = CURRENT_OPS.with(|slot| slot.get())?;
836    // SAFETY: `ptr` was installed by an enclosing `with_native_ops` and remains
837    // valid/aligned/initialized for that scope, preserving the trait object's
838    // data/vtable provenance. It is dereferenced only as a shared `&dyn`; any
839    // number of these may coexist across re-entrant helper calls, so aliasing is
840    // sound. The helper returns before the installation scope ends.
841    let ops: &dyn NativeOps = unsafe { &*ptr };
842    Some(f(ops))
843}
844
845/// The internal outcome of a completion helper before it is written to `out`.
846enum HelperOutcome {
847    Done(HelperResult),
848    Trap(u32),
849}
850
851/// The shared body of every completion `bamts_*` wrapper: validate the frame,
852/// find the dispatcher, build the [`HelperCall`], write the completion, and
853/// return the exact tag discriminant. Never unwinds; every failure becomes a
854/// `FatalTrap`.
855fn run_completion_helper(
856    frame: *mut ShadowFrame,
857    out: *mut Completion,
858    build: impl FnOnce(&mut NativeFrame<'_>, &dyn NativeOps) -> HelperResult,
859) -> u32 {
860    // There is no writable completion slot on this path. Return the exact fatal
861    // tag without dereferencing `out`; generated code treats the tag as control
862    // flow and never reads `out.value` after a FatalTrap.
863    if out.is_null() || !out.addr().is_multiple_of(align_of::<Completion>()) {
864        return CompletionTag::FatalTrap.as_u32();
865    }
866
867    let outcome = catch_unwind(AssertUnwindSafe(|| {
868        // SAFETY: the caller (generated native code) passes a frame it owns for
869        // this synchronous call; `frame_from_raw` rejects null/misaligned pointers.
870        let mut native_frame = match unsafe { frame_from_raw(frame) } {
871            Some(view) => view,
872            None => return HelperOutcome::Trap(TRAP_INVALID_FRAME),
873        };
874        match with_current_ops(|ops| build(&mut native_frame, ops)) {
875            Some(result) => HelperOutcome::Done(result),
876            None => HelperOutcome::Trap(TRAP_MISSING_NATIVE_OPS),
877        }
878    }));
879
880    let (tag, value) = match outcome {
881        Ok(HelperOutcome::Done(result)) => (result.tag, result.value),
882        Ok(HelperOutcome::Trap(id)) => (CompletionTag::FatalTrap, Value::int32(id)),
883        Err(_) => (CompletionTag::FatalTrap, Value::int32(TRAP_PANIC)),
884    };
885
886    // SAFETY: `out` is non-null and aligned (checked above); generated native
887    // code passes a valid, writable Completion out-parameter it owns for this
888    // synchronous call.
889    unsafe { core::ptr::write(out, Completion::new(value)) };
890    tag.as_u32()
891}
892
893/// Dispatches `call` through the current dispatcher inside a validated frame.
894/// Helper for wrappers whose `HelperCall` needs no frame data to build.
895#[inline]
896fn dispatch_simple(frame: *mut ShadowFrame, out: *mut Completion, call: HelperCall) -> u32 {
897    run_completion_helper(frame, out, |native_frame, ops| {
898        ops.dispatch(native_frame, call)
899    })
900}
901
902// -- The exact 30 exported C-ABI helpers -------------------------------------
903//
904// Parameter order and widths mirror `bamts_codegen::Helper::param_types`
905// exactly: `frame` (pointer) first, `out` (pointer) last, runtime `Value`s as
906// `u64`, and selectors/indices as `u32`. `bamts_truthy` is the sole exception:
907// no `out`, returns `0`/`1`.
908
909/// # Safety
910///
911/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
912/// range is disjoint from its header, and a live, aligned, writable `out` when
913/// this helper has one. Both remain valid and unaliased for the full call.
914///
915/// `bamts_load_constant(frame, const_id, out)`.
916#[unsafe(no_mangle)]
917pub unsafe extern "C" fn bamts_load_constant(
918    frame: *mut ShadowFrame,
919    const_id: u32,
920    out: *mut Completion,
921) -> u32 {
922    dispatch_simple(frame, out, HelperCall::LoadConstant { const_id })
923}
924
925/// # Safety
926///
927/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
928/// range is disjoint from its header, and a live, aligned, writable `out` when
929/// this helper has one. Both remain valid and unaliased for the full call.
930///
931/// `bamts_unary(frame, op, operand, out)`.
932#[unsafe(no_mangle)]
933pub unsafe extern "C" fn bamts_unary(
934    frame: *mut ShadowFrame,
935    op: u32,
936    operand: u64,
937    out: *mut Completion,
938) -> u32 {
939    dispatch_simple(
940        frame,
941        out,
942        HelperCall::Unary {
943            op,
944            operand: Value::from_bits(operand),
945        },
946    )
947}
948
949/// # Safety
950///
951/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
952/// range is disjoint from its header, and a live, aligned, writable `out` when
953/// this helper has one. Both remain valid and unaliased for the full call.
954///
955/// `bamts_binary(frame, op, left, right, out)`.
956#[unsafe(no_mangle)]
957pub unsafe extern "C" fn bamts_binary(
958    frame: *mut ShadowFrame,
959    op: u32,
960    left: u64,
961    right: u64,
962    out: *mut Completion,
963) -> u32 {
964    dispatch_simple(
965        frame,
966        out,
967        HelperCall::Binary {
968            op,
969            left: Value::from_bits(left),
970            right: Value::from_bits(right),
971        },
972    )
973}
974
975/// # Safety
976///
977/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
978/// range is disjoint from its header, and a live, aligned, writable `out` when
979/// this helper has one. Both remain valid and unaliased for the full call.
980///
981/// `bamts_create_object(frame, out)`.
982#[unsafe(no_mangle)]
983pub unsafe extern "C" fn bamts_create_object(frame: *mut ShadowFrame, out: *mut Completion) -> u32 {
984    dispatch_simple(frame, out, HelperCall::CreateObject)
985}
986
987/// # Safety
988///
989/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
990/// range is disjoint from its header, and a live, aligned, writable `out` when
991/// this helper has one. Both remain valid and unaliased for the full call.
992///
993/// `bamts_create_array(frame, out)`.
994#[unsafe(no_mangle)]
995pub unsafe extern "C" fn bamts_create_array(frame: *mut ShadowFrame, out: *mut Completion) -> u32 {
996    dispatch_simple(frame, out, HelperCall::CreateArray)
997}
998
999/// # Safety
1000///
1001/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1002/// range is disjoint from its header, and a live, aligned, writable `out`.
1003/// Both remain valid and unaliased for the full call.
1004///
1005/// `bamts_create_cell(frame, out)`.
1006#[unsafe(no_mangle)]
1007pub unsafe extern "C" fn bamts_create_cell(frame: *mut ShadowFrame, out: *mut Completion) -> u32 {
1008    dispatch_simple(frame, out, HelperCall::CreateCell)
1009}
1010
1011/// # Safety
1012///
1013/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1014/// range is disjoint from its header, and a live, aligned, writable `out` when
1015/// this helper has one. Both remain valid and unaliased for the full call.
1016///
1017/// `bamts_create_closure(frame, function_id, captures, out)`.
1018#[unsafe(no_mangle)]
1019pub unsafe extern "C" fn bamts_create_closure(
1020    frame: *mut ShadowFrame,
1021    function_id: u32,
1022    captures: u64,
1023    out: *mut Completion,
1024) -> u32 {
1025    dispatch_simple(
1026        frame,
1027        out,
1028        HelperCall::CreateClosure {
1029            function_id,
1030            captures: Value::from_bits(captures),
1031        },
1032    )
1033}
1034
1035/// # Safety
1036///
1037/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1038/// range is disjoint from its header, and a live, aligned, writable `out` when
1039/// this helper has one. Both remain valid and unaliased for the full call.
1040///
1041/// `bamts_get_property(frame, object, key, out)`.
1042#[unsafe(no_mangle)]
1043pub unsafe extern "C" fn bamts_get_property(
1044    frame: *mut ShadowFrame,
1045    object: u64,
1046    key: u64,
1047    out: *mut Completion,
1048) -> u32 {
1049    dispatch_simple(
1050        frame,
1051        out,
1052        HelperCall::GetProperty {
1053            object: Value::from_bits(object),
1054            key: Value::from_bits(key),
1055        },
1056    )
1057}
1058
1059/// # Safety
1060///
1061/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1062/// range is disjoint from its header, and a live, aligned, writable `out` when
1063/// this helper has one. Both remain valid and unaliased for the full call.
1064///
1065/// `bamts_set_property(frame, object, key, value, out)`.
1066#[unsafe(no_mangle)]
1067pub unsafe extern "C" fn bamts_set_property(
1068    frame: *mut ShadowFrame,
1069    object: u64,
1070    key: u64,
1071    value: u64,
1072    out: *mut Completion,
1073) -> u32 {
1074    dispatch_simple(
1075        frame,
1076        out,
1077        HelperCall::SetProperty {
1078            object: Value::from_bits(object),
1079            key: Value::from_bits(key),
1080            value: Value::from_bits(value),
1081        },
1082    )
1083}
1084
1085/// # Safety
1086///
1087/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1088/// range is disjoint from its header, and a live, aligned, writable `out` when
1089/// this helper has one. Both remain valid and unaliased for the full call.
1090///
1091/// `bamts_delete_property(frame, object, key, out)`.
1092#[unsafe(no_mangle)]
1093pub unsafe extern "C" fn bamts_delete_property(
1094    frame: *mut ShadowFrame,
1095    object: u64,
1096    key: u64,
1097    out: *mut Completion,
1098) -> u32 {
1099    dispatch_simple(
1100        frame,
1101        out,
1102        HelperCall::DeleteProperty {
1103            object: Value::from_bits(object),
1104            key: Value::from_bits(key),
1105        },
1106    )
1107}
1108
1109/// # Safety
1110///
1111/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1112/// range is disjoint from its header, and a live, aligned, writable `out` when
1113/// this helper has one. Both remain valid and unaliased for the full call.
1114///
1115/// `bamts_call(frame, callee, this, arguments, out)`.
1116#[unsafe(no_mangle)]
1117pub unsafe extern "C" fn bamts_call(
1118    frame: *mut ShadowFrame,
1119    callee: u64,
1120    this_value: u64,
1121    arguments: u64,
1122    out: *mut Completion,
1123) -> u32 {
1124    dispatch_simple(
1125        frame,
1126        out,
1127        HelperCall::Call {
1128            callee: Value::from_bits(callee),
1129            this_value: Value::from_bits(this_value),
1130            arguments: Value::from_bits(arguments),
1131        },
1132    )
1133}
1134
1135/// # Safety
1136///
1137/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1138/// range is disjoint from its header, and a live, aligned, writable `out` when
1139/// this helper has one. Both remain valid and unaliased for the full call.
1140///
1141/// `bamts_construct(frame, callee, arguments, out)`.
1142#[unsafe(no_mangle)]
1143pub unsafe extern "C" fn bamts_construct(
1144    frame: *mut ShadowFrame,
1145    callee: u64,
1146    arguments: u64,
1147    out: *mut Completion,
1148) -> u32 {
1149    dispatch_simple(
1150        frame,
1151        out,
1152        HelperCall::Construct {
1153            callee: Value::from_bits(callee),
1154            arguments: Value::from_bits(arguments),
1155        },
1156    )
1157}
1158
1159/// # Safety
1160///
1161/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1162/// range is disjoint from its header, and a live, aligned, writable `out` when
1163/// this helper has one. Both remain valid and unaliased for the full call.
1164///
1165/// `bamts_import(frame, specifier, out)`.
1166#[unsafe(no_mangle)]
1167pub unsafe extern "C" fn bamts_import(
1168    frame: *mut ShadowFrame,
1169    specifier: u32,
1170    out: *mut Completion,
1171) -> u32 {
1172    dispatch_simple(frame, out, HelperCall::Import { specifier })
1173}
1174
1175/// Validates `frame` and runs the tagless truthy helper for `value`.
1176///
1177/// Returns `None` when the frame is invalid or no dispatcher is installed.
1178fn truthy_from_raw(frame: *mut ShadowFrame, value: u64) -> Option<bool> {
1179    // SAFETY: generated native code passes a frame it owns for this
1180    // synchronous call; `frame_from_raw` rejects null or misaligned pointers
1181    // before any dereference.
1182    let mut native_frame = unsafe { frame_from_raw(frame) }?;
1183    with_current_ops(|ops| ops.truthy(&mut native_frame, Value::from_bits(value)))
1184}
1185
1186/// # Safety
1187///
1188/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1189/// range is disjoint from its header, and a live, aligned, writable `out` when
1190/// this helper has one. Both remain valid and unaliased for the full call.
1191///
1192/// `bamts_truthy(frame, value) -> u32`. Total; never writes `out` (there is
1193/// none) and never throws. Returns `0` on an invalid frame or missing
1194/// dispatcher, the only channel available to a tagless helper.
1195#[unsafe(no_mangle)]
1196pub unsafe extern "C" fn bamts_truthy(frame: *mut ShadowFrame, value: u64) -> u32 {
1197    let outcome = catch_unwind(AssertUnwindSafe(|| truthy_from_raw(frame, value)));
1198    match outcome {
1199        Ok(Some(true)) => 1,
1200        _ => 0,
1201    }
1202}
1203
1204/// # Safety
1205///
1206/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1207/// range is disjoint from its header, and a live, aligned, writable `out` when
1208/// this helper has one. Both remain valid and unaliased for the full call.
1209///
1210/// `bamts_resume_value(frame, out)`.
1211#[unsafe(no_mangle)]
1212pub unsafe extern "C" fn bamts_resume_value(frame: *mut ShadowFrame, out: *mut Completion) -> u32 {
1213    dispatch_simple(frame, out, HelperCall::ResumeValue)
1214}
1215
1216/// # Safety
1217///
1218/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1219/// range is disjoint from its header, and a live, aligned, writable `out`. Both
1220/// remain valid and unaliased for the full call.
1221///
1222/// `bamts_consume_fuel(frame, amount, out)`.
1223#[unsafe(no_mangle)]
1224pub unsafe extern "C" fn bamts_consume_fuel(
1225    frame: *mut ShadowFrame,
1226    amount: u32,
1227    out: *mut Completion,
1228) -> u32 {
1229    dispatch_simple(frame, out, HelperCall::ConsumeFuel { amount })
1230}
1231
1232/// # Safety
1233///
1234/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1235/// range is disjoint from its header, and a live, aligned, writable `out` when
1236/// this helper has one. Both remain valid and unaliased for the full call.
1237///
1238/// `bamts_define_accessor(frame, object, key, accessor, kind, out)`.
1239#[unsafe(no_mangle)]
1240pub unsafe extern "C" fn bamts_define_accessor(
1241    frame: *mut ShadowFrame,
1242    object: u64,
1243    key: u64,
1244    accessor: u64,
1245    kind: u32,
1246    out: *mut Completion,
1247) -> u32 {
1248    dispatch_simple(
1249        frame,
1250        out,
1251        HelperCall::DefineAccessor {
1252            object: Value::from_bits(object),
1253            key: Value::from_bits(key),
1254            accessor: Value::from_bits(accessor),
1255            kind,
1256        },
1257    )
1258}
1259
1260/// # Safety
1261///
1262/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1263/// range is disjoint from its header, and a live, aligned, writable `out` when
1264/// this helper has one. Both remain valid and unaliased for the full call.
1265///
1266/// `bamts_load_global(frame, name, out)`.
1267#[unsafe(no_mangle)]
1268pub unsafe extern "C" fn bamts_load_global(
1269    frame: *mut ShadowFrame,
1270    name: u32,
1271    out: *mut Completion,
1272) -> u32 {
1273    dispatch_simple(frame, out, HelperCall::LoadGlobal { name })
1274}
1275
1276/// # Safety
1277///
1278/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1279/// range is disjoint from its header, and a live, aligned, writable `out` when
1280/// this helper has one. Both remain valid and unaliased for the full call.
1281///
1282/// `bamts_store_global(frame, name, value, out)`.
1283#[unsafe(no_mangle)]
1284pub unsafe extern "C" fn bamts_store_global(
1285    frame: *mut ShadowFrame,
1286    name: u32,
1287    value: u64,
1288    out: *mut Completion,
1289) -> u32 {
1290    dispatch_simple(
1291        frame,
1292        out,
1293        HelperCall::StoreGlobal {
1294            name,
1295            value: Value::from_bits(value),
1296        },
1297    )
1298}
1299
1300/// # Safety
1301///
1302/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1303/// range is disjoint from its header, and a live, aligned, writable `out` when
1304/// this helper has one. Both remain valid and unaliased for the full call.
1305///
1306/// `bamts_typeof_global(frame, name, out)`.
1307#[unsafe(no_mangle)]
1308pub unsafe extern "C" fn bamts_typeof_global(
1309    frame: *mut ShadowFrame,
1310    name: u32,
1311    out: *mut Completion,
1312) -> u32 {
1313    dispatch_simple(frame, out, HelperCall::TypeOfGlobal { name })
1314}
1315
1316/// # Safety
1317///
1318/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1319/// range is disjoint from its header, and a live, aligned, writable `out` when
1320/// this helper has one. Both remain valid and unaliased for the full call.
1321///
1322/// `bamts_load_this(frame, out)`.
1323#[unsafe(no_mangle)]
1324pub unsafe extern "C" fn bamts_load_this(frame: *mut ShadowFrame, out: *mut Completion) -> u32 {
1325    dispatch_simple(frame, out, HelperCall::LoadThis)
1326}
1327
1328/// # Safety
1329///
1330/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1331/// range is disjoint from its header, and a live, aligned, writable `out` when
1332/// this helper has one. Both remain valid and unaliased for the full call.
1333///
1334/// `bamts_load_arguments(frame, out)`.
1335#[unsafe(no_mangle)]
1336pub unsafe extern "C" fn bamts_load_arguments(
1337    frame: *mut ShadowFrame,
1338    out: *mut Completion,
1339) -> u32 {
1340    dispatch_simple(frame, out, HelperCall::LoadArguments)
1341}
1342
1343/// # Safety
1344///
1345/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1346/// range is disjoint from its header, and a live, aligned, writable `out` when
1347/// this helper has one. Both remain valid and unaliased for the full call.
1348///
1349/// `bamts_load_new_target(frame, out)`.
1350#[unsafe(no_mangle)]
1351pub unsafe extern "C" fn bamts_load_new_target(
1352    frame: *mut ShadowFrame,
1353    out: *mut Completion,
1354) -> u32 {
1355    dispatch_simple(frame, out, HelperCall::LoadNewTarget)
1356}
1357
1358/// # Safety
1359///
1360/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1361/// range is disjoint from its header, and a live, aligned, writable `out` when
1362/// this helper has one. Both remain valid and unaliased for the full call.
1363///
1364/// `bamts_array_push(frame, array, value, out)`.
1365#[unsafe(no_mangle)]
1366pub unsafe extern "C" fn bamts_array_push(
1367    frame: *mut ShadowFrame,
1368    array: u64,
1369    value: u64,
1370    out: *mut Completion,
1371) -> u32 {
1372    dispatch_simple(
1373        frame,
1374        out,
1375        HelperCall::ArrayPush {
1376            array: Value::from_bits(array),
1377            value: Value::from_bits(value),
1378        },
1379    )
1380}
1381
1382/// # Safety
1383///
1384/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1385/// range is disjoint from its header, and a live, aligned, writable `out` when
1386/// this helper has one. Both remain valid and unaliased for the full call.
1387///
1388/// `bamts_array_extend(frame, array, iterable, out)`.
1389#[unsafe(no_mangle)]
1390pub unsafe extern "C" fn bamts_array_extend(
1391    frame: *mut ShadowFrame,
1392    array: u64,
1393    iterable: u64,
1394    out: *mut Completion,
1395) -> u32 {
1396    dispatch_simple(
1397        frame,
1398        out,
1399        HelperCall::ArrayExtend {
1400            array: Value::from_bits(array),
1401            iterable: Value::from_bits(iterable),
1402        },
1403    )
1404}
1405
1406/// # Safety
1407///
1408/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1409/// range is disjoint from its header, and a live, aligned, writable `out` when
1410/// this helper has one. Both remain valid and unaliased for the full call.
1411///
1412/// `bamts_object_spread(frame, target, source, out)`.
1413#[unsafe(no_mangle)]
1414pub unsafe extern "C" fn bamts_object_spread(
1415    frame: *mut ShadowFrame,
1416    target: u64,
1417    source: u64,
1418    out: *mut Completion,
1419) -> u32 {
1420    dispatch_simple(
1421        frame,
1422        out,
1423        HelperCall::ObjectSpread {
1424            target: Value::from_bits(target),
1425            source: Value::from_bits(source),
1426        },
1427    )
1428}
1429
1430/// # Safety
1431///
1432/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1433/// range is disjoint from its header, and a live, aligned, writable `out` when
1434/// this helper has one. Both remain valid and unaliased for the full call.
1435///
1436/// `bamts_set_prototype(frame, object, prototype, out)`.
1437#[unsafe(no_mangle)]
1438pub unsafe extern "C" fn bamts_set_prototype(
1439    frame: *mut ShadowFrame,
1440    object: u64,
1441    prototype: u64,
1442    out: *mut Completion,
1443) -> u32 {
1444    dispatch_simple(
1445        frame,
1446        out,
1447        HelperCall::SetPrototype {
1448            object: Value::from_bits(object),
1449            prototype: Value::from_bits(prototype),
1450        },
1451    )
1452}
1453
1454/// # Safety
1455///
1456/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1457/// range is disjoint from its header, and a live, aligned, writable `out` when
1458/// this helper has one. Both remain valid and unaliased for the full call.
1459///
1460/// `bamts_create_private_name(frame, description, out)`.
1461#[unsafe(no_mangle)]
1462pub unsafe extern "C" fn bamts_create_private_name(
1463    frame: *mut ShadowFrame,
1464    description: u32,
1465    out: *mut Completion,
1466) -> u32 {
1467    dispatch_simple(frame, out, HelperCall::CreatePrivateName { description })
1468}
1469
1470/// # Safety
1471///
1472/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1473/// range is disjoint from its header, and a live, aligned, writable `out` when
1474/// this helper has one. Both remain valid and unaliased for the full call.
1475///
1476/// `bamts_create_regexp(frame, pattern, flags, out)`.
1477#[unsafe(no_mangle)]
1478pub unsafe extern "C" fn bamts_create_regexp(
1479    frame: *mut ShadowFrame,
1480    pattern: u32,
1481    flags: u32,
1482    out: *mut Completion,
1483) -> u32 {
1484    dispatch_simple(frame, out, HelperCall::CreateRegExp { pattern, flags })
1485}
1486
1487/// # Safety
1488///
1489/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1490/// range is disjoint from its header, and a live, aligned, writable `out` when
1491/// this helper has one. Both remain valid and unaliased for the full call.
1492///
1493/// `bamts_get_iterator(frame, src, kind, out)`.
1494#[unsafe(no_mangle)]
1495pub unsafe extern "C" fn bamts_get_iterator(
1496    frame: *mut ShadowFrame,
1497    src: u64,
1498    kind: u32,
1499    out: *mut Completion,
1500) -> u32 {
1501    dispatch_simple(
1502        frame,
1503        out,
1504        HelperCall::GetIterator {
1505            src: Value::from_bits(src),
1506            kind,
1507        },
1508    )
1509}
1510
1511/// # Safety
1512///
1513/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1514/// range is disjoint from its header, and a live, aligned, writable `out` when
1515/// this helper has one. Both remain valid and unaliased for the full call.
1516///
1517/// `bamts_iterator_next(frame, iterator, done_reg, value_reg, out)`.
1518#[unsafe(no_mangle)]
1519pub unsafe extern "C" fn bamts_iterator_next(
1520    frame: *mut ShadowFrame,
1521    iterator: u64,
1522    done_reg: u32,
1523    value_reg: u32,
1524    out: *mut Completion,
1525) -> u32 {
1526    run_completion_helper(frame, out, |native_frame, ops| {
1527        if done_reg >= native_frame.handle_len() || value_reg >= native_frame.handle_len() {
1528            return HelperResult {
1529                tag: CompletionTag::FatalTrap,
1530                value: Value::int32(TRAP_INVALID_REGISTER),
1531            };
1532        }
1533        ops.dispatch(
1534            native_frame,
1535            HelperCall::IteratorNext {
1536                iterator: Value::from_bits(iterator),
1537                done_reg,
1538                value_reg,
1539            },
1540        )
1541    })
1542}
1543
1544/// # Safety
1545///
1546/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
1547/// range is disjoint from its header, and a live, aligned, writable `out` when
1548/// this helper has one. Both remain valid and unaliased for the full call.
1549///
1550/// `bamts_export(frame, name, src, out)`.
1551#[unsafe(no_mangle)]
1552pub unsafe extern "C" fn bamts_export(
1553    frame: *mut ShadowFrame,
1554    name: u32,
1555    src: u64,
1556    out: *mut Completion,
1557) -> u32 {
1558    dispatch_simple(
1559        frame,
1560        out,
1561        HelperCall::Export {
1562            name,
1563            src: Value::from_bits(src),
1564        },
1565    )
1566}
1567
1568// Compile-time signature parity with `bamts_codegen::Helper::param_types`. Each
1569// `const _` binds a `bamts_*` export to its exact ABI signature (every runtime
1570// `Value` is a 64-bit scalar, so `u64`; selectors/indices are `u32`). Any drift
1571// in codegen's helper parameter order, count, or width stops this crate from
1572// compiling, so the JIT/AOT linker can never silently mismatch a helper.
1573const _: unsafe extern "C" fn(*mut ShadowFrame, u32, *mut Completion) -> u32 = bamts_load_constant; // 0
1574const _: unsafe extern "C" fn(*mut ShadowFrame, u32, u64, *mut Completion) -> u32 = bamts_unary; // 1
1575const _: unsafe extern "C" fn(*mut ShadowFrame, u32, u64, u64, *mut Completion) -> u32 =
1576    bamts_binary; // 2
1577const _: unsafe extern "C" fn(*mut ShadowFrame, *mut Completion) -> u32 = bamts_create_object; // 3
1578const _: unsafe extern "C" fn(*mut ShadowFrame, *mut Completion) -> u32 = bamts_create_array; // 4
1579const _: unsafe extern "C" fn(*mut ShadowFrame, u32, u64, *mut Completion) -> u32 =
1580    bamts_create_closure; // 5
1581const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, *mut Completion) -> u32 =
1582    bamts_get_property; // 6
1583const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, u64, *mut Completion) -> u32 =
1584    bamts_set_property; // 7
1585const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, *mut Completion) -> u32 =
1586    bamts_delete_property; // 8
1587const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, u64, *mut Completion) -> u32 = bamts_call; // 9
1588const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, *mut Completion) -> u32 = bamts_construct; // 10
1589const _: unsafe extern "C" fn(*mut ShadowFrame, u32, *mut Completion) -> u32 = bamts_import; // 11
1590const _: unsafe extern "C" fn(*mut ShadowFrame, u64) -> u32 = bamts_truthy; // 12 (no out)
1591const _: unsafe extern "C" fn(*mut ShadowFrame, *mut Completion) -> u32 = bamts_resume_value; // 13
1592const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, u64, u32, *mut Completion) -> u32 =
1593    bamts_define_accessor; // 14
1594const _: unsafe extern "C" fn(*mut ShadowFrame, u32, *mut Completion) -> u32 = bamts_load_global; // 15
1595const _: unsafe extern "C" fn(*mut ShadowFrame, u32, u64, *mut Completion) -> u32 =
1596    bamts_store_global; // 16
1597const _: unsafe extern "C" fn(*mut ShadowFrame, u32, *mut Completion) -> u32 = bamts_typeof_global; // 17
1598const _: unsafe extern "C" fn(*mut ShadowFrame, *mut Completion) -> u32 = bamts_load_this; // 18
1599const _: unsafe extern "C" fn(*mut ShadowFrame, *mut Completion) -> u32 = bamts_load_arguments; // 19
1600const _: unsafe extern "C" fn(*mut ShadowFrame, *mut Completion) -> u32 = bamts_load_new_target; // 20
1601const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, *mut Completion) -> u32 =
1602    bamts_array_push; // 21
1603const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, *mut Completion) -> u32 =
1604    bamts_array_extend; // 22
1605const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, *mut Completion) -> u32 =
1606    bamts_object_spread; // 23
1607const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u64, *mut Completion) -> u32 =
1608    bamts_set_prototype; // 24
1609const _: unsafe extern "C" fn(*mut ShadowFrame, u32, *mut Completion) -> u32 =
1610    bamts_create_private_name; // 25
1611const _: unsafe extern "C" fn(*mut ShadowFrame, u32, u32, *mut Completion) -> u32 =
1612    bamts_create_regexp; // 26
1613const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u32, *mut Completion) -> u32 =
1614    bamts_get_iterator; // 27
1615const _: unsafe extern "C" fn(*mut ShadowFrame, u64, u32, u32, *mut Completion) -> u32 =
1616    bamts_iterator_next; // 28
1617const _: unsafe extern "C" fn(*mut ShadowFrame, u32, u64, *mut Completion) -> u32 = bamts_export; // 29
1618const _: unsafe extern "C" fn(*mut ShadowFrame, u32, *mut Completion) -> u32 = bamts_consume_fuel; // 30
1619
1620// -- Native entry invocation seam --------------------------------------------
1621
1622/// A finalized native entry point: `extern "C" fn(frame, out) -> tag`.
1623pub type NativeEntryFn = unsafe extern "C" fn(*mut ShadowFrame, *mut Completion) -> u32;
1624
1625/// A non-self-referential seam for invoking a compiled native entry by its
1626/// `(module_id, function_id)` identity. A JIT backend implements this over its
1627/// `JITModule`; a linked AOT image ([`LinkedProgram`]) implements it over its
1628/// unit table. The runtime engine stores `&dyn NativeEntryTable` and routes
1629/// nested `CreateClosure` re-entry through it.
1630pub trait NativeEntryTable {
1631    /// The exact canonical [`bamts_bytecode::Program::encode`] bytes compiled into these entries.
1632    ///
1633    /// Callers must compare these bytes with the supplied program before any native entry runs.
1634    fn program_bytes(&self) -> &[u8];
1635
1636    /// Invokes the entry for `(module_id, function_id)`, returning its completion tag.
1637    fn invoke(
1638        &self,
1639        module_id: u32,
1640        function_id: u32,
1641        frame: &mut ShadowFrame,
1642        out: &mut Completion,
1643    ) -> Result<CompletionTag, AbiError>;
1644}
1645
1646/// Calls a raw native entry with unique references, mapping the raw `u32` result
1647/// to a [`CompletionTag`] (an out-of-range tag becomes `FatalTrap`).
1648///
1649/// # Safety
1650///
1651/// `entry` must be a finalized native entry with the exact
1652/// `extern "C" fn(*mut ShadowFrame, *mut Completion) -> u32` ABI, and its code
1653/// must remain mapped for the duration of the call.
1654unsafe fn call_native_entry(
1655    entry: NativeEntryFn,
1656    frame: &mut ShadowFrame,
1657    out: &mut Completion,
1658) -> CompletionTag {
1659    // SAFETY: `entry` upholds the native-entry ABI (caller contract); `frame`
1660    // and `out` are unique valid references, so the raw pointers are valid and
1661    // unaliased for the synchronous call.
1662    let raw = unsafe { entry(frame as *mut ShadowFrame, out as *mut Completion) };
1663    match CompletionTag::from_u32(raw) {
1664        Some(tag) => tag,
1665        None => {
1666            *out = Completion::new(Value::int32(TRAP_INVALID_COMPLETION_TAG));
1667            CompletionTag::FatalTrap
1668        }
1669    }
1670}
1671
1672// -- AOT image linkage (types + validator are always available) --------------
1673
1674/// The little-endian image magic, `b"BMTSAOT1"`.
1675pub const AOT_MAGIC: u64 = u64::from_le_bytes(*b"BMTSAOT1");
1676/// The supported AOT image ABI version.
1677pub const AOT_ABI_VERSION: u32 = 3;
1678
1679/// One compiled function in a linked AOT image.
1680#[repr(C)]
1681#[derive(Clone, Copy, Debug)]
1682pub struct UnitDescriptor {
1683    /// The bytecode function id this entry implements.
1684    pub function_id: u32,
1685    /// The bytecode module id containing `function_id`.
1686    pub module_id: u32,
1687    /// The finalized native entry.
1688    pub entry: NativeEntryFn,
1689}
1690
1691/// The C-layout header of a linked AOT program image, exported by generated
1692/// code as the external symbol `bamts_program_descriptor`.
1693#[repr(C)]
1694#[derive(Clone, Copy, Debug)]
1695pub struct ProgramDescriptor {
1696    /// [`AOT_MAGIC`].
1697    pub magic: u64,
1698    /// [`AOT_ABI_VERSION`].
1699    pub abi_version: u32,
1700    /// Reserved flags; must be zero.
1701    pub flags: u32,
1702    /// The embedded verified bytecode image.
1703    pub bytecode: *const u8,
1704    /// The length of `bytecode` in bytes.
1705    pub bytecode_len: usize,
1706    /// The unit table.
1707    pub units: *const UnitDescriptor,
1708    /// The number of units.
1709    pub unit_count: usize,
1710    /// The entry function id (must appear in `units` with `entry_module`).
1711    pub entry_function: u32,
1712    /// The entry module id.
1713    pub entry_module: u32,
1714}
1715
1716/// A validated, borrow-checked view of a linked AOT image. The bytecode is
1717/// carried opaquely (native code never decodes it); reconstruction/verification
1718/// is the runtime's job.
1719pub struct LinkedProgram<'a> {
1720    bytecode: &'a [u8],
1721    units: &'a [UnitDescriptor],
1722    entry_module: u32,
1723    entry_function: u32,
1724}
1725
1726impl<'a> LinkedProgram<'a> {
1727    /// Validates a program descriptor into a borrowed linked view.
1728    ///
1729    /// Checks magic, ABI version, zeroed flags, non-null and non-empty bytecode
1730    /// and unit tables, overflow-safe slice extents, unit identities sorted and
1731    /// unique by `(module_id, function_id)`, and that the tuple entry is present.
1732    /// Layout is only defined on 64-bit targets; other widths yield
1733    /// [`AbiError::UnsupportedPointerWidth`].
1734    ///
1735    /// # Safety
1736    ///
1737    /// `descriptor`'s `bytecode`/`units` pointers must address `bytecode_len`
1738    /// bytes / `unit_count` [`UnitDescriptor`]s that remain valid and immutable
1739    /// for `'a`. The generated `bamts_program_descriptor` satisfies this for
1740    /// `'static`; a caller validating a synthetic descriptor guarantees it.
1741    pub unsafe fn from_descriptor(
1742        descriptor: &'a ProgramDescriptor,
1743    ) -> Result<LinkedProgram<'a>, AbiError> {
1744        if size_of::<usize>() != 8 {
1745            return Err(AbiError::UnsupportedPointerWidth {
1746                bits: usize::BITS as u16,
1747            });
1748        }
1749        if descriptor.magic != AOT_MAGIC {
1750            return Err(AbiError::BadMagic {
1751                found: descriptor.magic,
1752            });
1753        }
1754        if descriptor.abi_version != AOT_ABI_VERSION {
1755            return Err(AbiError::UnsupportedAbiVersion {
1756                found: descriptor.abi_version,
1757            });
1758        }
1759        if descriptor.flags != 0 {
1760            return Err(AbiError::NonZeroFlags {
1761                flags: descriptor.flags,
1762            });
1763        }
1764
1765        if descriptor.bytecode_len == 0 {
1766            return Err(AbiError::EmptyBytecode);
1767        }
1768        if descriptor.bytecode.is_null() {
1769            return Err(AbiError::NullBytecode);
1770        }
1771        if descriptor.bytecode_len > isize::MAX as usize {
1772            return Err(AbiError::LengthOverflow);
1773        }
1774
1775        if descriptor.unit_count == 0 {
1776            return Err(AbiError::EmptyUnits);
1777        }
1778        if descriptor.units.is_null() {
1779            return Err(AbiError::NullUnits);
1780        }
1781        let unit_bytes = descriptor
1782            .unit_count
1783            .checked_mul(size_of::<UnitDescriptor>())
1784            .ok_or(AbiError::LengthOverflow)?;
1785        if unit_bytes > isize::MAX as usize {
1786            return Err(AbiError::LengthOverflow);
1787        }
1788
1789        // SAFETY: `bytecode` is non-null and its length is bounded by
1790        // `isize::MAX`; the caller contract guarantees the memory stays valid
1791        // and immutable for `'a`.
1792        let bytecode =
1793            unsafe { core::slice::from_raw_parts(descriptor.bytecode, descriptor.bytecode_len) };
1794        // SAFETY: `units` is non-null and `unit_count * size_of::<UnitDescriptor>()`
1795        // is bounded by `isize::MAX`; the caller contract guarantees the memory
1796        // stays valid and immutable for `'a`.
1797        let units = unsafe { core::slice::from_raw_parts(descriptor.units, descriptor.unit_count) };
1798
1799        let mut entry_present = false;
1800        let mut previous = None;
1801        for unit in units {
1802            let identity = (unit.module_id, unit.function_id);
1803            if identity == (descriptor.entry_module, descriptor.entry_function) {
1804                entry_present = true;
1805            }
1806            if let Some((previous_module_id, previous_function_id)) = previous {
1807                match identity.cmp(&(previous_module_id, previous_function_id)) {
1808                    core::cmp::Ordering::Less => {
1809                        return Err(AbiError::UnsortedUnits {
1810                            previous_module_id,
1811                            previous_function_id,
1812                            module_id: unit.module_id,
1813                            function_id: unit.function_id,
1814                        });
1815                    }
1816                    core::cmp::Ordering::Equal => {
1817                        return Err(AbiError::DuplicateFunction {
1818                            module_id: unit.module_id,
1819                            function_id: unit.function_id,
1820                        });
1821                    }
1822                    core::cmp::Ordering::Greater => {}
1823                }
1824            }
1825            previous = Some(identity);
1826        }
1827        if !entry_present {
1828            return Err(AbiError::EntryFunctionMissing {
1829                module_id: descriptor.entry_module,
1830                function_id: descriptor.entry_function,
1831            });
1832        }
1833
1834        Ok(LinkedProgram {
1835            bytecode,
1836            units,
1837            entry_module: descriptor.entry_module,
1838            entry_function: descriptor.entry_function,
1839        })
1840    }
1841
1842    /// The embedded canonical [`bamts_bytecode::Program::encode`] bytes (opaque to native code).
1843    #[inline]
1844    #[must_use]
1845    pub fn bytecode(&self) -> &'a [u8] {
1846        self.bytecode
1847    }
1848
1849    /// The validated unit table.
1850    #[inline]
1851    #[must_use]
1852    pub fn units(&self) -> &'a [UnitDescriptor] {
1853        self.units
1854    }
1855
1856    /// The entry module id.
1857    #[inline]
1858    #[must_use]
1859    pub fn entry_module(&self) -> u32 {
1860        self.entry_module
1861    }
1862
1863    /// The entry function id.
1864    #[inline]
1865    #[must_use]
1866    pub fn entry_function(&self) -> u32 {
1867        self.entry_function
1868    }
1869
1870    /// The unit for `(module_id, function_id)`, if present.
1871    #[must_use]
1872    pub fn unit(&self, module_id: u32, function_id: u32) -> Option<&'a UnitDescriptor> {
1873        self.units
1874            .binary_search_by_key(&(module_id, function_id), |unit| {
1875                (unit.module_id, unit.function_id)
1876            })
1877            .ok()
1878            .map(|index| &self.units[index])
1879    }
1880}
1881
1882impl NativeEntryTable for LinkedProgram<'_> {
1883    fn program_bytes(&self) -> &[u8] {
1884        self.bytecode
1885    }
1886
1887    fn invoke(
1888        &self,
1889        module_id: u32,
1890        function_id: u32,
1891        frame: &mut ShadowFrame,
1892        out: &mut Completion,
1893    ) -> Result<CompletionTag, AbiError> {
1894        let unit = self
1895            .unit(module_id, function_id)
1896            .ok_or(AbiError::UnknownFunction {
1897                module_id,
1898                function_id,
1899            })?;
1900        // SAFETY: `unit.entry` is a finalized native entry installed by the AOT
1901        // image, upholding the native-entry ABI; its code stays mapped for the
1902        // program's lifetime.
1903        Ok(unsafe { call_native_entry(unit.entry, frame, out) })
1904    }
1905}
1906
1907/// Compile-time AOT layout assertions (64-bit targets only, per the ABI).
1908#[cfg(target_pointer_width = "64")]
1909const _: () = {
1910    use core::mem::{align_of, offset_of, size_of};
1911
1912    // UnitDescriptor: { u32, u32, fn ptr } => 16 bytes, 8-aligned.
1913    assert!(size_of::<UnitDescriptor>() == 16);
1914    assert!(align_of::<UnitDescriptor>() == 8);
1915    assert!(offset_of!(UnitDescriptor, function_id) == 0);
1916    assert!(offset_of!(UnitDescriptor, module_id) == 4);
1917    assert!(offset_of!(UnitDescriptor, entry) == 8);
1918
1919    // ProgramDescriptor: 56 bytes, 8-aligned, fields at fixed offsets.
1920    assert!(size_of::<ProgramDescriptor>() == 56);
1921    assert!(align_of::<ProgramDescriptor>() == 8);
1922    assert!(offset_of!(ProgramDescriptor, magic) == 0);
1923    assert!(offset_of!(ProgramDescriptor, abi_version) == 8);
1924    assert!(offset_of!(ProgramDescriptor, flags) == 12);
1925    assert!(offset_of!(ProgramDescriptor, bytecode) == 16);
1926    assert!(offset_of!(ProgramDescriptor, bytecode_len) == 24);
1927    assert!(offset_of!(ProgramDescriptor, units) == 32);
1928    assert!(offset_of!(ProgramDescriptor, unit_count) == 40);
1929    assert!(offset_of!(ProgramDescriptor, entry_function) == 48);
1930    assert!(offset_of!(ProgramDescriptor, entry_module) == 52);
1931};
1932
1933/// A typed AOT/entry-linkage failure.
1934#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1935pub enum AbiError {
1936    /// The target pointer width is not 64-bit.
1937    UnsupportedPointerWidth {
1938        /// The offending width in bits.
1939        bits: u16,
1940    },
1941    /// The descriptor magic did not match [`AOT_MAGIC`].
1942    BadMagic {
1943        /// The observed magic.
1944        found: u64,
1945    },
1946    /// The descriptor ABI version is not [`AOT_ABI_VERSION`].
1947    UnsupportedAbiVersion {
1948        /// The observed version.
1949        found: u32,
1950    },
1951    /// A reserved `flags` field was non-zero.
1952    NonZeroFlags {
1953        /// The observed flags.
1954        flags: u32,
1955    },
1956    /// The bytecode pointer was null with a non-zero length.
1957    NullBytecode,
1958    /// The bytecode image was empty.
1959    EmptyBytecode,
1960    /// The unit pointer was null with a non-zero count.
1961    NullUnits,
1962    /// The unit table was empty.
1963    EmptyUnits,
1964    /// A slice extent overflowed `isize::MAX`.
1965    LengthOverflow,
1966    /// Unit identities are not sorted by `(module_id, function_id)`.
1967    UnsortedUnits {
1968        /// The preceding module id.
1969        previous_module_id: u32,
1970        /// The preceding function id.
1971        previous_function_id: u32,
1972        /// The out-of-order module id.
1973        module_id: u32,
1974        /// The out-of-order function id.
1975        function_id: u32,
1976    },
1977    /// Two units shared a `(module_id, function_id)` identity.
1978    DuplicateFunction {
1979        /// The duplicated module id.
1980        module_id: u32,
1981        /// The duplicated function id.
1982        function_id: u32,
1983    },
1984    /// The declared tuple entry was absent from the unit table.
1985    EntryFunctionMissing {
1986        /// The missing module id.
1987        module_id: u32,
1988        /// The missing function id.
1989        function_id: u32,
1990    },
1991    /// [`NativeEntryTable::invoke`] was asked for an unknown function identity.
1992    UnknownFunction {
1993        /// The requested module id.
1994        module_id: u32,
1995        /// The requested function id.
1996        function_id: u32,
1997    },
1998}
1999
2000impl fmt::Display for AbiError {
2001    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2002        match self {
2003            AbiError::UnsupportedPointerWidth { bits } => {
2004                write!(f, "AOT image requires a 64-bit target, not {bits}-bit")
2005            }
2006            AbiError::BadMagic { found } => {
2007                write!(f, "AOT image magic {found:#018x} != {AOT_MAGIC:#018x}")
2008            }
2009            AbiError::UnsupportedAbiVersion { found } => {
2010                write!(f, "AOT image ABI version {found} != {AOT_ABI_VERSION}")
2011            }
2012            AbiError::NonZeroFlags { flags } => {
2013                write!(f, "AOT image flags {flags:#x} must be zero")
2014            }
2015            AbiError::NullBytecode => f.write_str("AOT image bytecode pointer is null"),
2016            AbiError::EmptyBytecode => f.write_str("AOT image bytecode is empty"),
2017            AbiError::NullUnits => f.write_str("AOT image unit pointer is null"),
2018            AbiError::EmptyUnits => f.write_str("AOT image unit table is empty"),
2019            AbiError::LengthOverflow => f.write_str("AOT image slice extent overflows isize::MAX"),
2020            AbiError::UnsortedUnits {
2021                previous_module_id,
2022                previous_function_id,
2023                module_id,
2024                function_id,
2025            } => write!(
2026                f,
2027                "AOT unit ({module_id}, {function_id}) follows ({previous_module_id}, {previous_function_id}) out of order"
2028            ),
2029            AbiError::DuplicateFunction {
2030                module_id,
2031                function_id,
2032            } => write!(
2033                f,
2034                "AOT image has duplicate native function ({module_id}, {function_id})"
2035            ),
2036            AbiError::EntryFunctionMissing {
2037                module_id,
2038                function_id,
2039            } => write!(
2040                f,
2041                "AOT image entry function ({module_id}, {function_id}) is absent"
2042            ),
2043            AbiError::UnknownFunction {
2044                module_id,
2045                function_id,
2046            } => write!(
2047                f,
2048                "no native entry for function ({module_id}, {function_id})"
2049            ),
2050        }
2051    }
2052}
2053
2054impl std::error::Error for AbiError {}
2055
2056/// Reads and validates the generated AOT image descriptor.
2057///
2058/// Feature-gated (`aot-image`) because it references the external
2059/// # Safety
2060///
2061/// The caller must provide a live, uniquely owned `frame` whose nonempty handle
2062/// range is disjoint from its header, and a live, aligned, writable `out` when
2063/// this helper has one. Both remain valid and unaliased for the full call.
2064///
2065/// `bamts_program_descriptor` symbol, which only exists in a fully linked AOT
2066/// binary. The descriptor types and [`LinkedProgram::from_descriptor`] validator
2067/// are always available for synthetic use and testing.
2068#[cfg(feature = "aot-image")]
2069pub fn linked_program() -> Result<LinkedProgram<'static>, AbiError> {
2070    unsafe extern "C" {
2071        static bamts_program_descriptor: ProgramDescriptor;
2072    }
2073    // SAFETY: `bamts_program_descriptor` is the generated 'static AOT image; its
2074    // pointer fields address 'static, immutable bytecode and unit tables, so
2075    // reading it and borrowing for 'static is sound.
2076    unsafe { LinkedProgram::from_descriptor(&bamts_program_descriptor) }
2077}
2078
2079// -- JIT entry (feature `jit-entry`) -----------------------------------------
2080
2081#[cfg(feature = "jit-entry")]
2082pub use jit::JitEntry;
2083
2084#[cfg(feature = "jit-entry")]
2085mod jit {
2086    use core::marker::PhantomData;
2087
2088    use cranelift_jit::JITModule;
2089    use cranelift_module::FuncId;
2090
2091    use super::{Completion, CompletionTag, NativeEntryFn, ShadowFrame, call_native_entry};
2092
2093    /// A finalized JIT entry point, bound to its owning `JITModule`'s lifetime so
2094    /// the code cannot be invoked after the module frees its memory.
2095    pub struct JitEntry<'m> {
2096        entry: NativeEntryFn,
2097        _module: PhantomData<&'m JITModule>,
2098    }
2099
2100    impl<'m> JitEntry<'m> {
2101        /// Resolves the finalized entry for `func` in `module`.
2102        ///
2103        /// `func` must have been defined and finalized in `module` with the
2104        /// native-entry signature `(frame, out) -> tag` (every lowered function
2105        /// does). The returned entry borrows `module` for `'m`, so it cannot
2106        /// outlive the code it points at.
2107        #[must_use]
2108        pub fn new(module: &'m JITModule, func: FuncId) -> JitEntry<'m> {
2109            let ptr = module.get_finalized_function(func);
2110            // SAFETY: `ptr` is the finalized machine code for `func` — initialized
2111            // and non-null — living in the module's executable mapping
2112            // (provenance). Every lowered function is emitted with the
2113            // native-entry ABI `extern "C" fn(*mut ShadowFrame, *mut Completion)
2114            // -> u32`, so the thin code pointer and `NativeEntryFn` share size and
2115            // alignment and the reinterpretation is valid (no bounds involved).
2116            // The code stays mapped for `'m` because `JitEntry` borrows `module`.
2117            let entry: NativeEntryFn =
2118                unsafe { core::mem::transmute::<*const u8, NativeEntryFn>(ptr) };
2119            JitEntry {
2120                entry,
2121                _module: PhantomData,
2122            }
2123        }
2124
2125        /// The raw finalized entry pointer.
2126        #[inline]
2127        #[must_use]
2128        pub fn entry_fn(&self) -> NativeEntryFn {
2129            self.entry
2130        }
2131
2132        /// Invokes the entry, returning its completion tag.
2133        pub fn invoke(&self, frame: &mut ShadowFrame, out: &mut Completion) -> CompletionTag {
2134            // SAFETY: `entry` is a finalized native entry with the native-entry
2135            // ABI, and its module (`'m`) outlives `self`, so the code is mapped.
2136            unsafe { call_native_entry(self.entry, frame, out) }
2137        }
2138    }
2139}
2140
2141#[cfg(test)]
2142mod tests {
2143    fn test_bamts_load_this(f: *mut ShadowFrame, o: *mut Completion) -> u32 {
2144        unsafe { super::bamts_load_this(f, o) }
2145    }
2146    fn test_bamts_call(f: *mut ShadowFrame, a: u64, t: u64, x: u64, o: *mut Completion) -> u32 {
2147        unsafe { super::bamts_call(f, a, t, x, o) }
2148    }
2149    fn test_bamts_binary(f: *mut ShadowFrame, op: u32, l: u64, r: u64, o: *mut Completion) -> u32 {
2150        unsafe { super::bamts_binary(f, op, l, r, o) }
2151    }
2152    fn test_bamts_truthy(f: *mut ShadowFrame, v: u64) -> u32 {
2153        unsafe { super::bamts_truthy(f, v) }
2154    }
2155    fn test_bamts_consume_fuel(f: *mut ShadowFrame, amount: u32, o: *mut Completion) -> u32 {
2156        unsafe { super::bamts_consume_fuel(f, amount, o) }
2157    }
2158    fn test_bamts_iterator_next(
2159        f: *mut ShadowFrame,
2160        i: u64,
2161        d: u32,
2162        v: u32,
2163        o: *mut Completion,
2164    ) -> u32 {
2165        unsafe { super::bamts_iterator_next(f, i, d, v, o) }
2166    }
2167    fn test_bamts_create_object(f: *mut ShadowFrame, o: *mut Completion) -> u32 {
2168        unsafe { super::bamts_create_object(f, o) }
2169    }
2170    fn test_bamts_load_global(f: *mut ShadowFrame, n: u32, o: *mut Completion) -> u32 {
2171        unsafe { super::bamts_load_global(f, n, o) }
2172    }
2173
2174    use super::*;
2175    use crate::{Completion, CompletionTag, ShadowFrame, Value};
2176    use std::cell::Cell;
2177    use std::panic::{AssertUnwindSafe, catch_unwind};
2178
2179    /// The codegen helper table, restated here as the parity fixture. A direct
2180    /// comparison to `bamts_codegen::Helper` is impossible (codegen depends on
2181    /// this crate under `host-jit`, so importing it would be a cycle), so this
2182    /// literal is the pinned contract; it must stay byte-identical to
2183    /// # Safety
2184    ///
2185    /// The caller must provide a live, uniquely owned `frame` whose nonempty handle
2186    /// range is disjoint from its header, and a live, aligned, writable `out` when
2187    /// this helper has one. Both remain valid and unaliased for the full call.
2188    ///
2189    /// `bamts_codegen::Helper::{external_index, symbol}`.
2190    const CODEGEN_HELPERS: [(u32, &str); 32] = [
2191        (0, "bamts_load_constant"),
2192        (1, "bamts_unary"),
2193        (2, "bamts_binary"),
2194        (3, "bamts_create_object"),
2195        (4, "bamts_create_array"),
2196        (5, "bamts_create_closure"),
2197        (6, "bamts_get_property"),
2198        (7, "bamts_set_property"),
2199        (8, "bamts_delete_property"),
2200        (9, "bamts_call"),
2201        (10, "bamts_construct"),
2202        (11, "bamts_import"),
2203        (12, "bamts_truthy"),
2204        (13, "bamts_resume_value"),
2205        (14, "bamts_define_accessor"),
2206        (15, "bamts_load_global"),
2207        (16, "bamts_store_global"),
2208        (17, "bamts_typeof_global"),
2209        (18, "bamts_load_this"),
2210        (19, "bamts_load_arguments"),
2211        (20, "bamts_load_new_target"),
2212        (21, "bamts_array_push"),
2213        (22, "bamts_array_extend"),
2214        (23, "bamts_object_spread"),
2215        (24, "bamts_set_prototype"),
2216        (25, "bamts_create_private_name"),
2217        (26, "bamts_create_regexp"),
2218        (27, "bamts_get_iterator"),
2219        (28, "bamts_iterator_next"),
2220        (29, "bamts_export"),
2221        (30, "bamts_consume_fuel"),
2222        (31, "bamts_create_cell"),
2223    ];
2224
2225    /// A recording dispatcher: captures the last call and returns a fixed
2226    /// result. Uses interior mutability since [`NativeOps`] dispatches on `&self`.
2227    struct Recorder {
2228        last: Cell<Option<HelperCall>>,
2229        truthy_calls: Cell<u32>,
2230        truthy_answer: Cell<bool>,
2231        result: HelperResult,
2232    }
2233
2234    impl Recorder {
2235        fn normal(value: Value) -> Recorder {
2236            Recorder {
2237                last: Cell::new(None),
2238                truthy_calls: Cell::new(0),
2239                truthy_answer: Cell::new(false),
2240                result: HelperResult::normal(value),
2241            }
2242        }
2243    }
2244
2245    impl NativeOps for Recorder {
2246        fn truthy(&self, _frame: &mut NativeFrame<'_>, value: Value) -> bool {
2247            self.truthy_calls.set(self.truthy_calls.get() + 1);
2248            self.last.set(Some(HelperCall::Truthy { value }));
2249            self.truthy_answer.get()
2250        }
2251
2252        fn dispatch(&self, frame: &mut NativeFrame<'_>, call: HelperCall) -> HelperResult {
2253            self.last.set(Some(call));
2254            if let HelperCall::IteratorNext {
2255                done_reg,
2256                value_reg,
2257                ..
2258            } = call
2259            {
2260                frame.set_register(done_reg, Value::TRUE);
2261                frame.set_register(value_reg, Value::int32(9));
2262            }
2263            self.result
2264        }
2265    }
2266
2267    /// A dispatcher whose `dispatch` always panics, to exercise the boundary.
2268    struct Panicky;
2269
2270    impl NativeOps for Panicky {
2271        fn truthy(&self, _frame: &mut NativeFrame<'_>, _value: Value) -> bool {
2272            panic!("truthy panic")
2273        }
2274
2275        fn dispatch(&self, _frame: &mut NativeFrame<'_>, _call: HelperCall) -> HelperResult {
2276            panic!("dispatch panic")
2277        }
2278    }
2279
2280    /// Runs `f`, catching an expected panic at the boundary. Expected panics
2281    /// still print one line each; that is normal test behavior and avoids the
2282    /// flakiness of swapping the process-global panic hook under parallel tests.
2283    fn quietly<R>(f: impl FnOnce() -> R + std::panic::UnwindSafe) -> std::thread::Result<R> {
2284        catch_unwind(f)
2285    }
2286
2287    fn frame_with(regs: &mut [Value]) -> ShadowFrame {
2288        let len = u16::try_from(regs.len()).expect("register count fits u16");
2289        ShadowFrame::new(core::ptr::null_mut(), 0, 0, regs.as_mut_ptr(), len)
2290    }
2291
2292    /// A dispatcher that re-enters itself once on the outer `Call`: it fires a
2293    /// nested `bamts_load_this` on a distinct child frame through the same TLS
2294    /// `&self`, then mutates its own state after the nested call returns. This is
2295    /// the exact single-instance reentry pattern that `&mut self` could not
2296    /// support soundly. State is behind `Cell` (interior mutability).
2297    struct Reentrant {
2298        depth: Cell<u32>,
2299        max_depth: Cell<u32>,
2300        post_nested_ran: Cell<bool>,
2301    }
2302
2303    impl NativeOps for Reentrant {
2304        fn truthy(&self, _frame: &mut NativeFrame<'_>, _value: Value) -> bool {
2305            true
2306        }
2307
2308        fn dispatch(&self, _frame: &mut NativeFrame<'_>, call: HelperCall) -> HelperResult {
2309            let depth = self.depth.get();
2310            self.max_depth.set(self.max_depth.get().max(depth));
2311            if depth == 0 && matches!(call, HelperCall::Call { .. }) {
2312                self.depth.set(1);
2313                // Re-enter native code on a DISTINCT child frame; this dispatches
2314                // back into the same `&self` via the TLS seam.
2315                let mut child_regs = [Value::UNINITIALIZED; 1];
2316                let mut child_frame = frame_with(&mut child_regs);
2317                let mut child_out = Completion::new(Value::UNDEFINED);
2318                let nested = test_bamts_load_this(&mut child_frame, &mut child_out);
2319                assert_eq!(nested, CompletionTag::Normal.as_u32());
2320                self.depth.set(0);
2321                // Post-nested-call state mutation — UB under `&mut self`, sound here.
2322                self.post_nested_ran.set(true);
2323            }
2324            HelperResult::normal(Value::int32(depth as i32 as u32))
2325        }
2326    }
2327
2328    #[test]
2329    fn same_instance_reentry_mutates_state_after_nested_call() {
2330        let mut regs = [Value::UNINITIALIZED; 1];
2331        let mut frame = frame_with(&mut regs);
2332        let mut completion = Completion::new(Value::UNDEFINED);
2333        let mut ops = Reentrant {
2334            depth: Cell::new(0),
2335            max_depth: Cell::new(0),
2336            post_nested_ran: Cell::new(false),
2337        };
2338        let tag = with_native_ops(&mut ops, || {
2339            test_bamts_call(
2340                &mut frame,
2341                Value::UNDEFINED.to_bits(),
2342                Value::UNDEFINED.to_bits(),
2343                Value::UNDEFINED.to_bits(),
2344                &mut completion,
2345            )
2346        });
2347        assert_eq!(tag, CompletionTag::Normal.as_u32());
2348        // The nested dispatch re-entered the same instance (depth reached 1).
2349        assert_eq!(ops.max_depth.get(), 1);
2350        // And the outer dispatch resumed touching `self` after the nested return.
2351        assert!(ops.post_nested_ran.get());
2352    }
2353
2354    #[test]
2355    fn helper_symbols_and_indices_match_codegen() {
2356        assert_eq!(HELPER_COUNT as usize, CODEGEN_HELPERS.len());
2357        for (index, symbol) in CODEGEN_HELPERS {
2358            let helper = NativeHelper::from_u32(index).expect("dense index");
2359            assert_eq!(helper.as_u32(), index, "index for {helper:?}");
2360            assert_eq!(helper.symbol(), symbol, "symbol for {helper:?}");
2361        }
2362        // Dense and total: no index past the table, and the inverse rejects it.
2363        assert_eq!(NativeHelper::from_u32(HELPER_COUNT), None);
2364    }
2365
2366    #[test]
2367    fn helper_call_maps_to_its_helper() {
2368        assert_eq!(
2369            HelperCall::Binary {
2370                op: 0,
2371                left: Value::UNDEFINED,
2372                right: Value::UNDEFINED,
2373            }
2374            .helper(),
2375            NativeHelper::Binary
2376        );
2377        assert_eq!(HelperCall::ResumeValue.helper(), NativeHelper::ResumeValue);
2378        assert_eq!(
2379            HelperCall::Truthy { value: Value::TRUE }.helper(),
2380            NativeHelper::Truthy
2381        );
2382        assert_eq!(
2383            HelperCall::Export {
2384                name: 3,
2385                src: Value::NULL,
2386            }
2387            .helper(),
2388            NativeHelper::Export
2389        );
2390        assert_eq!(
2391            HelperCall::ConsumeFuel { amount: 1 }.helper(),
2392            NativeHelper::ConsumeFuel
2393        );
2394        assert_eq!(HelperCall::CreateCell.helper(), NativeHelper::CreateCell);
2395    }
2396
2397    #[test]
2398    fn exported_wrapper_dispatches_and_writes_completion() {
2399        let mut regs = [Value::UNINITIALIZED; 2];
2400        let mut frame = frame_with(&mut regs);
2401        let mut completion = Completion::new(Value::UNDEFINED);
2402        let mut ops = Recorder::normal(Value::int32(42));
2403        let tag = with_native_ops(&mut ops, || {
2404            test_bamts_binary(
2405                &mut frame,
2406                2,
2407                Value::int32(3).to_bits(),
2408                Value::int32(4).to_bits(),
2409                &mut completion,
2410            )
2411        });
2412        assert_eq!(tag, CompletionTag::Normal.as_u32());
2413        assert_eq!(completion.value.as_int32(), Some(42));
2414        assert_eq!(
2415            ops.last.get(),
2416            Some(HelperCall::Binary {
2417                op: 2,
2418                left: Value::int32(3),
2419                right: Value::int32(4),
2420            })
2421        );
2422    }
2423
2424    #[test]
2425    fn consume_fuel_wrapper_preserves_amount() {
2426        let mut regs = [Value::UNINITIALIZED; 1];
2427        let mut frame = frame_with(&mut regs);
2428        let mut completion = Completion::new(Value::UNDEFINED);
2429        let mut ops = Recorder::normal(Value::UNDEFINED);
2430        let tag = with_native_ops(&mut ops, || {
2431            test_bamts_consume_fuel(&mut frame, 7, &mut completion)
2432        });
2433        assert_eq!(tag, CompletionTag::Normal.as_u32());
2434        assert_eq!(ops.last.get(), Some(HelperCall::ConsumeFuel { amount: 7 }));
2435    }
2436
2437    #[test]
2438    fn truthy_wrapper_routes_to_truthy_not_dispatch() {
2439        let mut regs = [Value::UNINITIALIZED; 1];
2440        let mut frame = frame_with(&mut regs);
2441        let mut ops = Recorder::normal(Value::UNDEFINED);
2442        ops.truthy_answer.set(true);
2443        let truthy = with_native_ops(&mut ops, || {
2444            test_bamts_truthy(&mut frame, Value::int32(1).to_bits())
2445        });
2446        assert_eq!(truthy, 1);
2447        assert_eq!(ops.truthy_calls.get(), 1);
2448
2449        ops.truthy_answer.set(false);
2450        let falsy = with_native_ops(&mut ops, || {
2451            test_bamts_truthy(&mut frame, Value::int32(0).to_bits())
2452        });
2453        assert_eq!(falsy, 0);
2454        assert_eq!(ops.truthy_calls.get(), 2);
2455    }
2456
2457    #[test]
2458    fn iterator_next_writes_both_registers() {
2459        let mut regs = [Value::UNINITIALIZED; 2];
2460        let mut frame = frame_with(&mut regs);
2461        let mut completion = Completion::new(Value::UNDEFINED);
2462        let mut ops = Recorder::normal(Value::UNDEFINED);
2463        let tag = with_native_ops(&mut ops, || {
2464            test_bamts_iterator_next(&mut frame, Value::NULL.to_bits(), 0, 1, &mut completion)
2465        });
2466        assert_eq!(tag, CompletionTag::Normal.as_u32());
2467        assert_eq!(regs[0], Value::TRUE);
2468        assert_eq!(regs[1], Value::int32(9));
2469    }
2470
2471    #[test]
2472    fn missing_dispatcher_is_a_fatal_trap() {
2473        let mut regs = [Value::UNINITIALIZED; 1];
2474        let mut frame = frame_with(&mut regs);
2475        let mut completion = Completion::new(Value::UNDEFINED);
2476        // No `with_native_ops` scope: no dispatcher installed.
2477        let tag = test_bamts_create_object(&mut frame, &mut completion);
2478        assert_eq!(tag, CompletionTag::FatalTrap.as_u32());
2479        assert_eq!(completion.value.as_int32(), Some(TRAP_MISSING_NATIVE_OPS));
2480        // The tagless helper has only `0` to report the failure with.
2481        assert_eq!(test_bamts_truthy(&mut frame, Value::TRUE.to_bits()), 0);
2482    }
2483
2484    #[test]
2485    fn invalid_frame_is_a_fatal_trap() {
2486        let mut completion = Completion::new(Value::UNDEFINED);
2487        let mut ops = Recorder::normal(Value::int32(1));
2488        let null = with_native_ops(&mut ops, || {
2489            test_bamts_create_object(core::ptr::null_mut(), &mut completion)
2490        });
2491        assert_eq!(null, CompletionTag::FatalTrap.as_u32());
2492        assert_eq!(completion.value.as_int32(), Some(TRAP_INVALID_FRAME));
2493        // A misaligned (non-null) pointer is rejected without a dereference.
2494        let misaligned = with_native_ops(&mut ops, || {
2495            test_bamts_create_object(
2496                core::ptr::null_mut::<ShadowFrame>().wrapping_byte_add(1),
2497                &mut completion,
2498            )
2499        });
2500        assert_eq!(misaligned, CompletionTag::FatalTrap.as_u32());
2501    }
2502
2503    #[test]
2504    fn dispatcher_panic_is_caught_as_fatal_trap() {
2505        let mut regs = [Value::UNINITIALIZED; 1];
2506        let mut frame = frame_with(&mut regs);
2507        let mut completion = Completion::new(Value::UNDEFINED);
2508        let mut ops = Panicky;
2509        let tag = quietly(AssertUnwindSafe(|| {
2510            with_native_ops(&mut ops, || {
2511                test_bamts_create_object(&mut frame, &mut completion)
2512            })
2513        }))
2514        .expect("wrapper must not unwind across the boundary");
2515        assert_eq!(tag, CompletionTag::FatalTrap.as_u32());
2516        assert_eq!(completion.value.as_int32(), Some(TRAP_PANIC));
2517    }
2518
2519    #[test]
2520    fn tls_nesting_restores_the_outer_dispatcher() {
2521        let mut regs = [Value::UNINITIALIZED; 1];
2522        let mut frame = frame_with(&mut regs);
2523        let mut completion = Completion::new(Value::UNDEFINED);
2524        let mut outer = Recorder::normal(Value::int32(1));
2525        let mut inner = Recorder::normal(Value::int32(2));
2526
2527        with_native_ops(&mut outer, || {
2528            test_bamts_create_object(&mut frame, &mut completion);
2529            assert_eq!(completion.value.as_int32(), Some(1));
2530            with_native_ops(&mut inner, || {
2531                test_bamts_create_object(&mut frame, &mut completion);
2532                assert_eq!(completion.value.as_int32(), Some(2));
2533            });
2534            // The inner scope has ended: the outer dispatcher is restored.
2535            test_bamts_create_object(&mut frame, &mut completion);
2536            assert_eq!(completion.value.as_int32(), Some(1));
2537        });
2538        // Both scopes ended: no dispatcher, so a fatal trap.
2539        let tag = test_bamts_create_object(&mut frame, &mut completion);
2540        assert_eq!(tag, CompletionTag::FatalTrap.as_u32());
2541    }
2542
2543    #[test]
2544    fn tls_is_restored_after_a_panicking_body() {
2545        let mut regs = [Value::UNINITIALIZED; 1];
2546        let mut frame = frame_with(&mut regs);
2547        let mut completion = Completion::new(Value::UNDEFINED);
2548        let mut ops = Recorder::normal(Value::int32(1));
2549
2550        let result = quietly(AssertUnwindSafe(|| {
2551            with_native_ops(&mut ops, || panic!("body panic"));
2552        }));
2553        assert!(result.is_err());
2554        // The guard restored the previous (empty) dispatcher on unwind.
2555        let tag = test_bamts_create_object(&mut frame, &mut completion);
2556        assert_eq!(tag, CompletionTag::FatalTrap.as_u32());
2557    }
2558
2559    #[test]
2560    fn native_frame_new_validates_metadata() {
2561        let mut regs = [Value::int32(1), Value::int32(2)];
2562        let base = regs.as_mut_ptr();
2563        let mut frame = ShadowFrame::new(core::ptr::null_mut(), 0, 3, base, 2);
2564        // Length mismatch against the frame header.
2565        {
2566            let mut short = [Value::int32(1)];
2567            assert!(NativeFrame::new(&mut frame, &mut short).is_none());
2568        }
2569        // Pointer mismatch against the frame header.
2570        {
2571            let mut other = [Value::int32(1), Value::int32(2)];
2572            assert!(NativeFrame::new(&mut frame, &mut other).is_none());
2573        }
2574        // Exact match.
2575        {
2576            let native = NativeFrame::new(&mut frame, &mut regs);
2577            assert!(native.is_some());
2578        }
2579    }
2580
2581    #[test]
2582    fn native_frame_from_raw_validates_and_addresses_registers() {
2583        // SAFETY: from_raw rejects a null or misaligned pointer before any
2584        // dereference, so neither call accesses memory.
2585        assert!(unsafe { NativeFrame::from_raw(core::ptr::null_mut()) }.is_none());
2586        assert!(
2587            unsafe {
2588                NativeFrame::from_raw(core::ptr::null_mut::<ShadowFrame>().wrapping_byte_add(1))
2589            }
2590            .is_none()
2591        );
2592
2593        let mut regs = [Value::int32(10), Value::int32(20)];
2594        let mut frame = ShadowFrame::new(core::ptr::null_mut(), 7, 11, regs.as_mut_ptr(), 2);
2595        {
2596            // SAFETY: `frame` is a live, unaliased local and its metadata points
2597            // at exactly the two initialized Values in `regs` for this scope.
2598            let mut native = unsafe { NativeFrame::from_raw(&mut frame) }.expect("valid frame");
2599            assert_eq!(native.handle_len(), 2);
2600            assert_eq!(native.module_id(), 11);
2601            assert_eq!(native.pc(), 7);
2602            assert_eq!(native.register(0), Value::int32(10));
2603            assert_eq!(native.try_register(5), None);
2604            native.set_register(1, Value::int32(99));
2605            assert!(native.try_set_register(1, Value::int32(99)));
2606            assert!(!native.try_set_register(5, Value::int32(0)));
2607            native.set_resume(3);
2608        }
2609        assert_eq!(frame.bytecode_pc, 3);
2610        assert_eq!(regs[1], Value::int32(99));
2611    }
2612
2613    #[test]
2614    fn native_frame_from_raw_rejects_handles_overlapping_header() {
2615        let mut frame = ShadowFrame::new(core::ptr::null_mut(), 0, 0, core::ptr::null_mut(), 1);
2616        frame.handles = core::ptr::addr_of_mut!(frame).cast::<Value>();
2617        // SAFETY: `frame` is live and aligned; this test intentionally supplies
2618        // malformed metadata to verify it is rejected before aliasing references.
2619        assert!(unsafe { NativeFrame::from_raw(&mut frame) }.is_none());
2620    }
2621
2622    unsafe extern "C" fn entry_returns_seven(
2623        _frame: *mut ShadowFrame,
2624        out: *mut Completion,
2625    ) -> u32 {
2626        // SAFETY: the test passes a valid, writable `Completion` out-parameter.
2627        unsafe { core::ptr::write(out, Completion::new(Value::int32(7))) };
2628        CompletionTag::Normal.as_u32()
2629    }
2630
2631    unsafe extern "C" fn entry_returns_invalid_tag(
2632        _frame: *mut ShadowFrame,
2633        out: *mut Completion,
2634    ) -> u32 {
2635        // SAFETY: the test passes a valid, writable completion pointer.
2636        unsafe { core::ptr::write(out, Completion::new(Value::int32(99))) };
2637        u32::MAX
2638    }
2639
2640    #[test]
2641    fn invalid_native_completion_tag_replaces_stale_output() {
2642        let mut regs: [Value; 0] = [];
2643        let mut frame = ShadowFrame::new(core::ptr::null_mut(), 0, 0, regs.as_mut_ptr(), 0);
2644        let mut out = Completion::new(Value::int32(123));
2645        // SAFETY: test entry has the native ABI and frame/output are live, unique.
2646        let tag = unsafe { call_native_entry(entry_returns_invalid_tag, &mut frame, &mut out) };
2647        assert_eq!(tag, CompletionTag::FatalTrap);
2648        assert_eq!(out.value.as_int32(), Some(TRAP_INVALID_COMPLETION_TAG));
2649    }
2650
2651    fn unit(module_id: u32, function_id: u32) -> UnitDescriptor {
2652        UnitDescriptor {
2653            function_id,
2654            module_id,
2655            entry: entry_returns_seven,
2656        }
2657    }
2658
2659    fn program(
2660        bytecode: &[u8],
2661        units: &[UnitDescriptor],
2662        entry_module: u32,
2663        entry_function: u32,
2664    ) -> ProgramDescriptor {
2665        ProgramDescriptor {
2666            magic: AOT_MAGIC,
2667            abi_version: AOT_ABI_VERSION,
2668            flags: 0,
2669            bytecode: bytecode.as_ptr(),
2670            bytecode_len: bytecode.len(),
2671            units: units.as_ptr(),
2672            unit_count: units.len(),
2673            entry_function,
2674            entry_module,
2675        }
2676    }
2677
2678    /// Validates a locally-built descriptor after proving its raw pointer fields
2679    /// describe the supplied backing slices.
2680    fn linked_of<'a>(
2681        descriptor: &'a ProgramDescriptor,
2682        bytecode: &'a [u8],
2683        units: &'a [UnitDescriptor],
2684    ) -> Result<LinkedProgram<'a>, AbiError> {
2685        assert_eq!(descriptor.bytecode_len, bytecode.len());
2686        assert!(bytecode.is_empty() || core::ptr::eq(descriptor.bytecode, bytecode.as_ptr()));
2687        assert_eq!(descriptor.unit_count, units.len());
2688        assert!(units.is_empty() || core::ptr::eq(descriptor.units, units.as_ptr()));
2689        // SAFETY: the pointer/length pairs were checked against live immutable
2690        // backing slices above; both slices outlive the returned program view.
2691        unsafe { LinkedProgram::from_descriptor(descriptor) }
2692    }
2693
2694    #[test]
2695    fn linked_program_validates_and_invokes_tuple_identities() {
2696        let bytecode = [1u8, 2, 3];
2697        let units = [unit(2, 4), unit(2, 5), unit(3, 5)];
2698        let descriptor = program(&bytecode, &units, 3, 5);
2699        let linked = linked_of(&descriptor, &bytecode, &units).expect("valid image");
2700        assert_eq!(linked.bytecode(), &[1, 2, 3]);
2701        assert_eq!(linked.program_bytes(), &[1, 2, 3]);
2702        assert_eq!(linked.units().len(), 3);
2703        assert_eq!(linked.entry_module(), 3);
2704        assert_eq!(linked.entry_function(), 5);
2705        assert!(linked.unit(2, 5).is_some());
2706        assert!(linked.unit(3, 5).is_some());
2707        assert!(linked.unit(3, 4).is_none());
2708
2709        let mut regs: [Value; 0] = [];
2710        let mut frame = ShadowFrame::new(core::ptr::null_mut(), 0, 3, regs.as_mut_ptr(), 0);
2711        let mut completion = Completion::new(Value::UNDEFINED);
2712        let tag = linked
2713            .invoke(3, 5, &mut frame, &mut completion)
2714            .expect("entry present");
2715        assert_eq!(tag, CompletionTag::Normal);
2716        assert_eq!(completion.value.as_int32(), Some(7));
2717        assert_eq!(
2718            linked.invoke(4, 5, &mut frame, &mut completion).err(),
2719            Some(AbiError::UnknownFunction {
2720                module_id: 4,
2721                function_id: 5,
2722            })
2723        );
2724    }
2725
2726    #[test]
2727    fn linked_program_rejects_malformed_descriptors() {
2728        let bytecode = [1u8, 2, 3];
2729        let units = [unit(2, 5)];
2730
2731        let mut bad_magic = program(&bytecode, &units, 2, 5);
2732        bad_magic.magic = 0;
2733        assert_eq!(
2734            linked_of(&bad_magic, &bytecode, &units).err(),
2735            Some(AbiError::BadMagic { found: 0 })
2736        );
2737
2738        let mut bad_version = program(&bytecode, &units, 2, 5);
2739        bad_version.abi_version = 1;
2740        assert_eq!(
2741            linked_of(&bad_version, &bytecode, &units).err(),
2742            Some(AbiError::UnsupportedAbiVersion { found: 1 })
2743        );
2744
2745        let mut bad_flags = program(&bytecode, &units, 2, 5);
2746        bad_flags.flags = 1;
2747        assert_eq!(
2748            linked_of(&bad_flags, &bytecode, &units).err(),
2749            Some(AbiError::NonZeroFlags { flags: 1 })
2750        );
2751
2752        let empty_bytecode = program(&[], &units, 2, 5);
2753        assert_eq!(
2754            linked_of(&empty_bytecode, &[], &units).err(),
2755            Some(AbiError::EmptyBytecode)
2756        );
2757
2758        let empty_units = program(&bytecode, &[], 2, 5);
2759        assert_eq!(
2760            linked_of(&empty_units, &bytecode, &[]).err(),
2761            Some(AbiError::EmptyUnits)
2762        );
2763    }
2764
2765    #[test]
2766    fn linked_program_rejects_unsorted_duplicate_and_missing_tuple_entries() {
2767        let bytecode = [1u8, 2, 3];
2768
2769        let unsorted = [unit(2, 5), unit(1, 9)];
2770        let unsorted_descriptor = program(&bytecode, &unsorted, 2, 5);
2771        assert_eq!(
2772            linked_of(&unsorted_descriptor, &bytecode, &unsorted).err(),
2773            Some(AbiError::UnsortedUnits {
2774                previous_module_id: 2,
2775                previous_function_id: 5,
2776                module_id: 1,
2777                function_id: 9,
2778            })
2779        );
2780
2781        let duplicate = [unit(2, 5), unit(2, 5)];
2782        let duplicate_descriptor = program(&bytecode, &duplicate, 2, 5);
2783        assert_eq!(
2784            linked_of(&duplicate_descriptor, &bytecode, &duplicate).err(),
2785            Some(AbiError::DuplicateFunction {
2786                module_id: 2,
2787                function_id: 5,
2788            })
2789        );
2790
2791        let units = [unit(2, 5), unit(3, 5)];
2792        let missing_entry = program(&bytecode, &units, 4, 5);
2793        assert_eq!(
2794            linked_of(&missing_entry, &bytecode, &units).err(),
2795            Some(AbiError::EntryFunctionMissing {
2796                module_id: 4,
2797                function_id: 5,
2798            })
2799        );
2800    }
2801
2802    #[test]
2803    fn null_out_is_a_fatal_trap_without_dereference() {
2804        let mut regs = [Value::UNINITIALIZED; 1];
2805        let mut frame = frame_with(&mut regs);
2806        let mut ops = Recorder::normal(Value::int32(1));
2807        // Null `out`: the wrapper returns the fatal tag and never writes a body.
2808        let tag = with_native_ops(&mut ops, || {
2809            test_bamts_load_global(&mut frame, 0, core::ptr::null_mut())
2810        });
2811        assert_eq!(tag, CompletionTag::FatalTrap.as_u32());
2812    }
2813
2814    #[test]
2815    fn iterator_next_out_of_range_register_is_fatal_trap() {
2816        let mut regs = [Value::UNINITIALIZED; 1];
2817        let mut frame = frame_with(&mut regs);
2818        let mut completion = Completion::new(Value::UNDEFINED);
2819        let mut ops = Recorder::normal(Value::UNDEFINED);
2820        let tag = with_native_ops(&mut ops, || {
2821            test_bamts_iterator_next(&mut frame, Value::NULL.to_bits(), 99, 0, &mut completion)
2822        });
2823        assert_eq!(tag, CompletionTag::FatalTrap.as_u32());
2824        assert_eq!(completion.value.as_int32(), Some(TRAP_INVALID_REGISTER));
2825        // The dispatcher was never reached, so no register was mutated.
2826        assert_eq!(regs[0], Value::UNINITIALIZED);
2827    }
2828}