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}