Skip to main content

bamts_codegen/
lib.rs

1//! Shared, backend-neutral Cranelift lowering for verified BamTS bytecode.
2//!
3//! This crate turns a canonical [`bamts_bytecode::Program<Verified>`] into
4//! Cranelift IR through [`lower_program`]. It retains one [`LoweredModule`] per
5//! program module, with module-local pools and module-qualified native symbols,
6//! for both feature-gated backends:
7//!
8//! * a `host-jit` backend that finalizes each `ir::Function` into executable
9//!   memory, and
10//! * an `aot` backend that emits each `ir::Function` into an object file.
11//!
12//! This slice performs **no** executable-memory allocation and **no** object
13//! linking; it only builds and verifies IR. Both later backends supply their
14//! own [`isa::TargetFrontendConfig`] (via `isa.frontend_config()`), so the ISA
15//! choice, calling convention, and pointer type stay outside this crate.
16//!
17//! # Entry ABI
18//!
19//! Every lowered function has the native-entry signature from the canonical
20//! execution plan (N5), matching `bamts_native::ShadowFrame` and
21//! `bamts_native::Completion`:
22//!
23//! ```text
24//! extern "C" fn(frame: *mut ShadowFrame, out: *mut Completion) -> u32
25//! ```
26//!
27//! * `frame` points at the register frame; `frame.handles` (offset 16) is the
28//!   `*mut Value` register array. `frame.bytecode_pc` (offset 8) records the
29//!   active instruction and carries the resume token after
30//!   [`Suspend`](#suspend-and-the-resume-helper).
31//! * `out` receives the completion value; the returned `u32` is a
32//!   `bamts_native::CompletionTag` discriminant (`Normal`/`Throw`/`Suspend`/
33//!   `FatalTrap`).
34//!
35//! # Register addressing convention
36//!
37//! Register `r[i]` lives at `frame.handles + i * 8` (one `Value`/`u64` slot).
38//! Every access derives the byte offset as `i64::from(register.get()) * 8`; the
39//! validation pass ([`validate_slots`]) proves this offset fits the `Offset32`
40//! used by loads and stores, so `u32` register ids and CLIF addresses never mix
41//! widths inconsistently. This holds for register ids well past 127: a slot
42//! offset is a full 32-bit displacement, not a signed byte.
43//!
44//! # Dynamic operands: no fixed windows, no constant-keyed properties
45//!
46//! The production ISA carries no fixed argument window and no constant-keyed
47//! property access. Calls and constructs take a single **arguments array** in a
48//! register (`Call`/`Construct` `arguments`), so spread and any arity flow
49//! through one `Value` handle with no pointer arithmetic. Property access takes
50//! its **key from a register** (`GetProperty`/`SetProperty`/`DeleteProperty`
51//! `key`), a `Value` the runtime coerces to a property key (string, symbol, or
52//! private name). Closures capture through an array register
53//! (`CreateClosure` `captures`), again a single `Value` handle.
54//!
55//! # Value semantics: the explicit helper ABI
56//!
57//! The bytecode algebra ([`bamts_bytecode::Instruction`]) is structural: the
58//! verifier proves definite initialization and CFG validity but assigns no
59//! value meaning. The NaN-boxed runtime `Value` requires tag dispatch that this
60//! IR-only slice must not open-code, so every operation whose result depends on
61//! runtime value semantics is lowered to a call into a declared [`Helper`]
62//! (`u1:<index>` external names a backend resolves to a C symbol). This crate
63//! declares each helper's ABI and control-flow contract; it never defines the
64//! helper body.
65//!
66//! Every value-producing helper follows one **completion ABI**:
67//! `fn(frame, <operands…>, out: *mut Completion) -> u32(tag)`. On
68//! `Normal` (0) the result is in `out.value`; on `Throw` the thrown handle is in
69//! `out.value` and control routes to a covering handler; `FatalTrap` always
70//! propagates to the runtime. Two exceptions to the "result in `out.value`"
71//! rule:
72//!
73//! * [`Helper::Truthy`] performs the total ToBoolean coercion and returns the
74//!   truth value directly as `0`/`1`; it never writes `out` and never throws.
75//! * [`Helper::IteratorNext`] writes **two** registers — it receives the `done`
76//!   and `value` register indices and, on `Normal`, writes both slots in the
77//!   frame directly (a single completion channel cannot carry two results);
78//!   `out.value` is used only to carry a thrown handle on `Throw`.
79//!
80//! A subset of the completion helpers is **total** (`Normal` only, never
81//! `Throw`/`FatalTrap`): [`Helper::TypeOfGlobal`], [`Helper::LoadThis`],
82//! [`Helper::LoadArguments`], [`Helper::LoadNewTarget`], and
83//! [`Helper::CreatePrivateName`]. They still use the completion ABI (result in
84//! `out.value`) but their abnormal edge is unreachable, so they never mark a
85//! handler block reachable.
86//!
87//! ## Opcode ledger (every variant has an explicit path)
88//!
89//! | Opcode              | Lowering                                                     |
90//! |---------------------|-------------------------------------------------------------|
91//! | `LoadConst`         | [`Helper::LoadConstant`] by `ConstantId` → `dst`            |
92//! | `Move`              | inline copy `handles[src]` → `handles[dst]`                 |
93//! | `Unary`             | [`Helper::Unary`] with the operator selector               |
94//! | `Binary`            | [`Helper::Binary`] with the operator selector              |
95//! | `CreateObject`      | [`Helper::CreateObject`] → `dst`                            |
96//! | `CreateArray`       | [`Helper::CreateArray`] → `dst`                             |
97//! | `CreateCell`        | [`Helper::CreateCell`] → `dst`                             |
98//! | `CreateClosure`     | [`Helper::CreateClosure`] (`function`, `captures` array)→dst|
99//! | `GetProperty`       | [`Helper::GetProperty`] (`object`, register `key`) → `dst`  |
100//! | `SetProperty`       | [`Helper::SetProperty`] (`object`, register `key`, `value`) |
101//! | `DeleteProperty`    | [`Helper::DeleteProperty`] (`object`, register `key`) → dst |
102//! | `DefineAccessor`    | [`Helper::DefineAccessor`] (`object`, `key`, `accessor`, kind)|
103//! | `Call`              | [`Helper::Call`] (`callee`, `this`, `arguments` array) → dst|
104//! | `Construct`         | [`Helper::Construct`] (`callee`, `arguments` array) → `dst` |
105//! | `LoadGlobal`        | [`Helper::LoadGlobal`] by string `name` → `dst`            |
106//! | `StoreGlobal`       | [`Helper::StoreGlobal`] (string `name`, `value`)          |
107//! | `TypeOfGlobal`      | [`Helper::TypeOfGlobal`] by string `name` → `dst` (total)  |
108//! | `LoadThis`          | [`Helper::LoadThis`] → `dst` (total)                       |
109//! | `LoadArguments`     | [`Helper::LoadArguments`] → `dst` (total)                  |
110//! | `LoadNewTarget`     | [`Helper::LoadNewTarget`] → `dst` (total)                  |
111//! | `ArrayPush`         | [`Helper::ArrayPush`] (`array`, `value`)                   |
112//! | `ArrayExtend`       | [`Helper::ArrayExtend`] (`array`, `iterable`)              |
113//! | `ObjectSpread`      | [`Helper::ObjectSpread`] (`target`, `source`)             |
114//! | `SetPrototype`      | [`Helper::SetPrototype`] (`object`, `prototype`)          |
115//! | `CreatePrivateName` | [`Helper::CreatePrivateName`] by `description` → dst (total)|
116//! | `CreateRegExp`      | [`Helper::CreateRegExp`] (`pattern`, `flags`) → `dst`      |
117//! | `GetIterator`       | [`Helper::GetIterator`] (`src`, kind) → `dst`             |
118//! | `IteratorNext`      | [`Helper::IteratorNext`] (`iterator`) → `done` + `value`   |
119//! | `Import`            | [`Helper::Import`] by string `specifier` → `dst`          |
120//! | `Export`            | [`Helper::Export`] (string `name`, `src`)                 |
121//! | `Jump`              | unconditional branch                                       |
122//! | `JumpIfTrue`        | [`Helper::Truthy`] then conditional branch                |
123//! | `JumpIfFalse`       | [`Helper::Truthy`] then conditional branch                |
124//! | `Return`            | `handles[value]` → `out.value`, return `Normal`           |
125//! | `Throw`             | route to covering handler (bind `catch_register`) or       |
126//! |                     | `out.value` + return `Throw`                              |
127//! | `Suspend`           | yield path + resume path via [`Helper::ResumeValue`]      |
128//! | `Halt`              | `undefined` → `out.value`, return `Normal`               |
129//!
130//! No opcode is silently dropped and none is lowered to a placeholder no-op.
131//!
132//! # Exceptions
133//!
134//! When a completion-helper call returns `Throw` and a bytecode handler covers
135//! the current pc, control branches to that handler's block after storing the
136//! thrown value (`out.value`) into the handler's `catch_register` slot; the
137//! explicit `Throw` opcode binds its operand into `catch_register` directly.
138//! `FatalTrap` bypasses handlers. When no handler covers the pc, the completion
139//! is returned to the caller.
140//!
141//! # Suspend and the resume helper
142//!
143//! `Suspend { dst, src, resume }` yields `src` and, when resumed, delivers the
144//! resumed value into `dst` before continuing at `resume`. The native entry ABI
145//! carries no resume input (`out.value` is the *yielded* value, not an input),
146//! so the resumed value is obtained through an explicit runtime contract rather
147//! than invented:
148//!
149//! * **Yield path** — store this suspend's resume token into `frame.bytecode_pc`
150//!   (`0` is a fresh call; the suspend at bytecode pc `P` uses token `P + 1`, so
151//!   tokens never collide with a fresh entry or with each other), write `src`
152//!   into `out.value`, and return `Suspend`.
153//! * **Resume path** — the dispatch prologue for token `P + 1` calls
154//!   [`Helper::ResumeValue`], which the runtime resolves to write the verified
155//!   resumed value for this frame into `out.value` (it may return `Throw` for
156//!   `generator.throw`, routed to a covering handler, or `FatalTrap`); the
157//!   resumed value is then stored into `dst` and control continues at `resume`.
158//!
159//! `bamts_bytecode` currently exposes no ABI for the resume input, so
160//! [`Helper::ResumeValue`] is a **new required contract** the runtime must
161//! provide for any module that suspends.
162
163#![forbid(unsafe_code)]
164
165#[cfg(feature = "host-jit")]
166mod jit;
167#[cfg(feature = "host-jit")]
168pub use jit::{JitError, JitProgram, compile_jit};
169
170#[cfg(feature = "aot")]
171mod aot;
172#[cfg(feature = "aot")]
173pub use aot::{AotError, AotObject, PROGRAM_DESCRIPTOR_SYMBOL, compile_aot};
174
175use std::collections::{BTreeMap, BTreeSet};
176use std::error::Error;
177use std::fmt;
178
179use bamts_bytecode::{
180    AccessorKind, BinaryOp, ExceptionHandler, FunctionId, Instruction, IteratorKind, Module,
181    ModuleId, Pc, Program, Register, UnaryOp, Verified,
182};
183use cranelift_codegen::ir::condcodes::IntCC;
184use cranelift_codegen::ir::{
185    AbiParam, Block, ExtFuncData, ExternalName, Function, InstBuilder, MemFlagsData, Signature,
186    Type, UserExternalName, UserFuncName, Value, types,
187};
188use cranelift_codegen::isa::{CallConv, TargetFrontendConfig};
189use cranelift_codegen::settings::{self, Flags};
190use cranelift_codegen::verifier::verify_function;
191use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
192
193// -- ABI layout (grounded in bamts_native::ShadowFrame / Completion / Value) --
194
195/// Byte offset of `ShadowFrame.bytecode_pc` (a `u32`).
196const SHADOW_FRAME_PC_OFFSET: i32 = 8;
197/// Byte offset of `ShadowFrame.module_id` (a `u32`).
198const SHADOW_FRAME_MODULE_OFFSET: i32 = 12;
199/// Byte offset of `ShadowFrame.handles` (a `*mut Value`).
200const SHADOW_FRAME_HANDLES_OFFSET: i32 = 16;
201/// Byte offset of `Completion.value` within the out-parameter.
202const COMPLETION_VALUE_OFFSET: i32 = 0;
203/// Size, in bytes, of one register slot (`Value` is a `u64`).
204const VALUE_BYTES: i64 = 8;
205
206/// Canonical `undefined`, matching `bamts_native::Value::UNDEFINED`
207/// (`boxed(TAG_UNDEFINED=3, 0)` = `0x7ff8… | 3<<48`).
208const UNDEFINED_BITS: i64 = 0x7ffb_0000_0000_0000;
209
210/// `TrapRecordId` written to `out.value` when resumed at an unknown token.
211const TRAP_INVALID_RESUME: i64 = 1;
212
213/// [`CompletionTag::Normal`] discriminant.
214const TAG_NORMAL: i64 = 0;
215/// [`CompletionTag::Throw`] discriminant.
216const TAG_THROW: i64 = 1;
217/// [`CompletionTag::Suspend`] discriminant.
218const TAG_SUSPEND: i64 = 2;
219/// [`CompletionTag::FatalTrap`] discriminant.
220const TAG_FATAL_TRAP: i64 = 3;
221
222/// Cranelift external-name namespace for lowered bytecode functions: a name
223/// `u0:<index>` refers to the lowered function whose [`FunctionId`] is `index`.
224pub const FUNCTION_NAMESPACE: u32 = 0;
225/// Cranelift external-name namespace for runtime helper imports: a name
226/// `u1:<index>` refers to the [`Helper`] with [`Helper::external_index`]
227/// equal to `index`.
228pub const HELPER_NAMESPACE: u32 = 1;
229
230// When the native crate is present, prove the hardcoded ABI facts still match
231// its authoritative definitions. This slice's default build does not depend on
232// bamts-native, so these are compiled only under the JIT-entry feature.
233#[cfg(feature = "host-jit")]
234const _: () = {
235    use core::mem::offset_of;
236    assert!(offset_of!(bamts_native::ShadowFrame, bytecode_pc) == SHADOW_FRAME_PC_OFFSET as usize);
237    assert!(
238        offset_of!(bamts_native::ShadowFrame, module_id) == SHADOW_FRAME_MODULE_OFFSET as usize
239    );
240    assert!(offset_of!(bamts_native::ShadowFrame, handles) == SHADOW_FRAME_HANDLES_OFFSET as usize);
241    assert!(core::mem::size_of::<bamts_native::Completion>() == VALUE_BYTES as usize);
242    assert!(bamts_native::Value::UNDEFINED.to_bits() == UNDEFINED_BITS as u64);
243    assert!(bamts_native::CompletionTag::Normal.as_u32() as i64 == TAG_NORMAL);
244    assert!(bamts_native::CompletionTag::Throw.as_u32() as i64 == TAG_THROW);
245    assert!(bamts_native::CompletionTag::Suspend.as_u32() as i64 == TAG_SUSPEND);
246    assert!(bamts_native::CompletionTag::FatalTrap.as_u32() as i64 == TAG_FATAL_TRAP);
247};
248
249// -- Runtime helpers ---------------------------------------------------------
250
251/// A runtime routine the lowered code calls but does not define. Backends
252/// resolve each [`Helper::symbol`] to an address (JIT) or relocation (AOT).
253///
254/// # Stable helper table
255///
256/// The variant order below is the canonical `external_index` order and the
257/// public contract a backend and the runtime link against. `frame` (`i64`)
258/// leads and `out` (`*mut Completion`, `i64`) trails every completion helper;
259/// runtime `Value`s are `i64`; small integer selectors and indices are `i32`.
260///
261/// | idx | variant             | params after `frame` (before `out`)          |
262/// |-----|---------------------|----------------------------------------------|
263/// |  0  | `LoadConstant`      | `const_id: i32`                              |
264/// |  1  | `Unary`             | `op: i32, operand: i64`                      |
265/// |  2  | `Binary`            | `op: i32, left: i64, right: i64`            |
266/// |  3  | `CreateObject`      | —                                            |
267/// |  4  | `CreateArray`       | —                                            |
268/// |  5  | `CreateClosure`     | `function_id: i32, captures: i64`           |
269/// |  6  | `GetProperty`       | `object: i64, key: i64`                     |
270/// |  7  | `SetProperty`       | `object: i64, key: i64, value: i64`         |
271/// |  8  | `DeleteProperty`    | `object: i64, key: i64`                     |
272/// |  9  | `Call`              | `callee: i64, this: i64, arguments: i64`    |
273/// | 10  | `Construct`         | `callee: i64, arguments: i64`               |
274/// | 11  | `Import`            | `specifier: i32`                            |
275/// | 12  | `Truthy`            | `value: i64` → `i32` (no `out`, total)      |
276/// | 13  | `ResumeValue`       | —                                            |
277/// | 14  | `DefineAccessor`    | `object: i64, key: i64, accessor: i64, kind: i32` |
278/// | 15  | `LoadGlobal`        | `name: i32`                                 |
279/// | 16  | `StoreGlobal`       | `name: i32, value: i64`                     |
280/// | 17  | `TypeOfGlobal`      | `name: i32` (total)                         |
281/// | 18  | `LoadThis`          | — (total)                                   |
282/// | 19  | `LoadArguments`     | — (total)                                   |
283/// | 20  | `LoadNewTarget`     | — (total)                                   |
284/// | 21  | `ArrayPush`         | `array: i64, value: i64`                    |
285/// | 22  | `ArrayExtend`       | `array: i64, iterable: i64`                 |
286/// | 23  | `ObjectSpread`      | `target: i64, source: i64`                  |
287/// | 24  | `SetPrototype`      | `object: i64, prototype: i64`               |
288/// | 25  | `CreatePrivateName` | `description: i32` (total)                  |
289/// | 26  | `CreateRegExp`      | `pattern: i32, flags: i32`                  |
290/// | 27  | `GetIterator`       | `src: i64, kind: i32`                        |
291/// | 28  | `IteratorNext`      | `iterator: i64, done_reg: i32, value_reg: i32` (two-write) |
292/// | 29  | `Export`            | `name: i32, src: i64`                        |
293/// | 30  | `ConsumeFuel`       | `amount: i32` (total except `FatalTrap`)     |
294/// | 31 | `CreateCell`        | —                                            |
295///
296/// Every helper except [`Helper::Truthy`] returns a
297/// `bamts_native::CompletionTag`. [`Helper::Truthy`] returns `0`/`1`.
298#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
299pub enum Helper {
300    /// `bamts_load_constant(frame, const_id, out)`: materialize the module
301    /// constant named by `const_id` into `out.value`.
302    LoadConstant,
303    /// `bamts_unary(frame, op, operand, out)`: apply the unary operator `op`
304    /// (see [`unary_op_selector`]) to `operand`.
305    Unary,
306    /// `bamts_binary(frame, op, left, right, out)`: apply the binary operator
307    /// `op` (see [`binary_op_selector`]) to `left` and `right`.
308    Binary,
309    /// `bamts_create_object(frame, out)`: fresh empty object into `out.value`.
310    CreateObject,
311    /// `bamts_create_array(frame, out)`: fresh empty array into `out.value`.
312    CreateArray,
313    /// `bamts_create_cell(frame, out)`: fresh compiler-private TDZ cell.
314    CreateCell,
315    /// `bamts_create_closure(frame, function_id, captures, out)`: materialize a
316    /// closure over the named function, binding the captured cells held in the
317    /// `captures` array value, into `out.value`. The runtime reads the callee's
318    /// `capture_count` to copy the leading capture registers.
319    CreateClosure,
320    /// `bamts_get_property(frame, object, key, out)`: `out.value = object[key]`,
321    /// with `key` a runtime value coerced to a property key.
322    GetProperty,
323    /// `bamts_set_property(frame, object, key, value, out)`: `object[key] = value`.
324    SetProperty,
325    /// `bamts_delete_property(frame, object, key, out)`:
326    /// `out.value = delete object[key]`.
327    DeleteProperty,
328    /// `bamts_call(frame, callee, this, arguments, out)`: call `callee` with
329    /// receiver `this` over the dynamic `arguments` array value.
330    Call,
331    /// `bamts_construct(frame, callee, arguments, out)`: construct with `callee`
332    /// over the dynamic `arguments` array value.
333    Construct,
334    /// `bamts_import(frame, specifier, out)`: import the module named by the
335    /// string constant `specifier` into `out.value`.
336    Import,
337    /// `bamts_truthy(frame, value) -> u32`: the total ToBoolean coercion,
338    /// returning `1` when `value` is truthy and `0` otherwise. Never throws and
339    /// never writes `out`.
340    Truthy,
341    /// `bamts_resume_value(frame, out)`: write the verified resumed value for
342    /// `frame` into `out.value`. Resolves the resume-input gap in the native
343    /// entry ABI (see the crate docs); may return `Throw` (`generator.throw`)
344    /// or `FatalTrap`.
345    ResumeValue,
346    /// `bamts_define_accessor(frame, object, key, accessor, kind, out)`: install
347    /// a getter or setter (`kind`, see [`accessor_kind_selector`]) under `key`.
348    DefineAccessor,
349    /// `bamts_load_global(frame, name, out)`: `out.value = globalThis[name]`;
350    /// throws a `ReferenceError` for an undeclared global.
351    LoadGlobal,
352    /// `bamts_store_global(frame, name, value, out)`: `globalThis[name] = value`.
353    StoreGlobal,
354    /// `bamts_typeof_global(frame, name, out)`: `out.value = typeof
355    /// globalThis[name]`; total, yielding `"undefined"` for an undeclared global.
356    TypeOfGlobal,
357    /// `bamts_load_this(frame, out)`: load the `this` binding into `out.value`.
358    /// Total.
359    LoadThis,
360    /// `bamts_load_arguments(frame, out)`: load the `arguments` object into
361    /// `out.value`. Total.
362    LoadArguments,
363    /// `bamts_load_new_target(frame, out)`: load `new.target` into `out.value`.
364    /// Total.
365    LoadNewTarget,
366    /// `bamts_array_push(frame, array, value, out)`: append `value` to `array`.
367    ArrayPush,
368    /// `bamts_array_extend(frame, array, iterable, out)`: spread `iterable` onto
369    /// the end of `array`.
370    ArrayExtend,
371    /// `bamts_object_spread(frame, target, source, out)`: copy the own
372    /// enumerable properties of `source` onto `target`.
373    ObjectSpread,
374    /// `bamts_set_prototype(frame, object, prototype, out)`: set the
375    /// `[[Prototype]]` of `object`.
376    SetPrototype,
377    /// `bamts_create_private_name(frame, description, out)`: create a fresh
378    /// private name into `out.value`. Total.
379    CreatePrivateName,
380    /// `bamts_create_regexp(frame, pattern, flags, out)`: build a `RegExp` from
381    /// the string-constant `pattern` and `flags` into `out.value`.
382    CreateRegExp,
383    /// `bamts_get_iterator(frame, src, kind, out)`: acquire an iterator over
384    /// `src` using protocol `kind` (see [`iterator_kind_selector`]).
385    GetIterator,
386    /// `bamts_iterator_next(frame, iterator, done_reg, value_reg, out)`: advance
387    /// `iterator`, writing the done flag into `handles[done_reg]` and the
388    /// produced value into `handles[value_reg]` directly (two writes). On
389    /// `Throw`, the thrown handle is in `out.value` and neither slot is written.
390    IteratorNext,
391    /// `bamts_export(frame, name, src, out)`: export the local value `src` under
392    /// the string constant `name`.
393    Export,
394    /// `bamts_consume_fuel(frame, amount, out)`: reserve `amount` bytecode
395    /// instructions from the shared machine budget. Returns `FatalTrap` on
396    /// exhaustion and never routes through a bytecode exception handler.
397    ConsumeFuel,
398}
399
400impl Helper {
401    /// The C symbol the backend links against.
402    #[must_use]
403    pub const fn symbol(self) -> &'static str {
404        match self {
405            Helper::LoadConstant => "bamts_load_constant",
406            Helper::Unary => "bamts_unary",
407            Helper::Binary => "bamts_binary",
408            Helper::CreateObject => "bamts_create_object",
409            Helper::CreateArray => "bamts_create_array",
410            Helper::CreateClosure => "bamts_create_closure",
411            Helper::GetProperty => "bamts_get_property",
412            Helper::SetProperty => "bamts_set_property",
413            Helper::DeleteProperty => "bamts_delete_property",
414            Helper::Call => "bamts_call",
415            Helper::Construct => "bamts_construct",
416            Helper::Import => "bamts_import",
417            Helper::Truthy => "bamts_truthy",
418            Helper::ResumeValue => "bamts_resume_value",
419            Helper::DefineAccessor => "bamts_define_accessor",
420            Helper::LoadGlobal => "bamts_load_global",
421            Helper::StoreGlobal => "bamts_store_global",
422            Helper::TypeOfGlobal => "bamts_typeof_global",
423            Helper::LoadThis => "bamts_load_this",
424            Helper::LoadArguments => "bamts_load_arguments",
425            Helper::LoadNewTarget => "bamts_load_new_target",
426            Helper::ArrayPush => "bamts_array_push",
427            Helper::ArrayExtend => "bamts_array_extend",
428            Helper::ObjectSpread => "bamts_object_spread",
429            Helper::SetPrototype => "bamts_set_prototype",
430            Helper::CreatePrivateName => "bamts_create_private_name",
431            Helper::CreateRegExp => "bamts_create_regexp",
432            Helper::GetIterator => "bamts_get_iterator",
433            Helper::IteratorNext => "bamts_iterator_next",
434            Helper::Export => "bamts_export",
435            Helper::ConsumeFuel => "bamts_consume_fuel",
436            Helper::CreateCell => "bamts_create_cell",
437        }
438    }
439
440    /// The stable helper index within [`HELPER_NAMESPACE`]; the `index` of the
441    /// `u1:<index>` external name a backend must resolve to [`Helper::symbol`].
442    #[must_use]
443    pub const fn external_index(self) -> u32 {
444        match self {
445            Helper::LoadConstant => 0,
446            Helper::Unary => 1,
447            Helper::Binary => 2,
448            Helper::CreateObject => 3,
449            Helper::CreateArray => 4,
450            Helper::CreateClosure => 5,
451            Helper::GetProperty => 6,
452            Helper::SetProperty => 7,
453            Helper::DeleteProperty => 8,
454            Helper::Call => 9,
455            Helper::Construct => 10,
456            Helper::Import => 11,
457            Helper::Truthy => 12,
458            Helper::ResumeValue => 13,
459            Helper::DefineAccessor => 14,
460            Helper::LoadGlobal => 15,
461            Helper::StoreGlobal => 16,
462            Helper::TypeOfGlobal => 17,
463            Helper::LoadThis => 18,
464            Helper::LoadArguments => 19,
465            Helper::LoadNewTarget => 20,
466            Helper::ArrayPush => 21,
467            Helper::ArrayExtend => 22,
468            Helper::ObjectSpread => 23,
469            Helper::SetPrototype => 24,
470            Helper::CreatePrivateName => 25,
471            Helper::CreateRegExp => 26,
472            Helper::GetIterator => 27,
473            Helper::IteratorNext => 28,
474            Helper::Export => 29,
475            Helper::ConsumeFuel => 30,
476            Helper::CreateCell => 31,
477        }
478    }
479
480    /// The helper for a [`HELPER_NAMESPACE`] external-name index, inverting
481    /// [`Helper::external_index`]. Returns `None` for an unknown index.
482    #[must_use]
483    pub const fn from_external_index(index: u32) -> Option<Helper> {
484        match index {
485            0 => Some(Helper::LoadConstant),
486            1 => Some(Helper::Unary),
487            2 => Some(Helper::Binary),
488            3 => Some(Helper::CreateObject),
489            4 => Some(Helper::CreateArray),
490            5 => Some(Helper::CreateClosure),
491            6 => Some(Helper::GetProperty),
492            7 => Some(Helper::SetProperty),
493            8 => Some(Helper::DeleteProperty),
494            9 => Some(Helper::Call),
495            10 => Some(Helper::Construct),
496            11 => Some(Helper::Import),
497            12 => Some(Helper::Truthy),
498            13 => Some(Helper::ResumeValue),
499            14 => Some(Helper::DefineAccessor),
500            15 => Some(Helper::LoadGlobal),
501            16 => Some(Helper::StoreGlobal),
502            17 => Some(Helper::TypeOfGlobal),
503            18 => Some(Helper::LoadThis),
504            19 => Some(Helper::LoadArguments),
505            20 => Some(Helper::LoadNewTarget),
506            21 => Some(Helper::ArrayPush),
507            22 => Some(Helper::ArrayExtend),
508            23 => Some(Helper::ObjectSpread),
509            24 => Some(Helper::SetPrototype),
510            25 => Some(Helper::CreatePrivateName),
511            26 => Some(Helper::CreateRegExp),
512            27 => Some(Helper::GetIterator),
513            28 => Some(Helper::IteratorNext),
514            29 => Some(Helper::Export),
515            30 => Some(Helper::ConsumeFuel),
516            31 => Some(Helper::CreateCell),
517            _ => None,
518        }
519    }
520
521    /// The helper parameter types, in order. `frame` and `out` pointers and
522    /// runtime `Value`s are `i64`; small integer selectors and indices are
523    /// `i32`.
524    const fn param_types(self) -> &'static [Type] {
525        match self {
526            // (frame, const_id, out)
527            Helper::LoadConstant => &[types::I64, types::I32, types::I64],
528            // (frame, op, operand, out)
529            Helper::Unary => &[types::I64, types::I32, types::I64, types::I64],
530            // (frame, op, left, right, out)
531            Helper::Binary => &[types::I64, types::I32, types::I64, types::I64, types::I64],
532            // (frame, out)
533            Helper::CreateObject
534            | Helper::CreateArray
535            | Helper::CreateCell
536            | Helper::ResumeValue
537            | Helper::LoadThis
538            | Helper::LoadArguments
539            | Helper::LoadNewTarget => &[types::I64, types::I64],
540            // (frame, index, out)
541            Helper::Import
542            | Helper::LoadGlobal
543            | Helper::TypeOfGlobal
544            | Helper::CreatePrivateName
545            | Helper::ConsumeFuel => &[types::I64, types::I32, types::I64],
546            // (frame, function_id, captures, out)
547            Helper::CreateClosure => &[types::I64, types::I32, types::I64, types::I64],
548            // (frame, object, key, out)
549            Helper::GetProperty | Helper::DeleteProperty => {
550                &[types::I64, types::I64, types::I64, types::I64]
551            }
552            // (frame, object, key, value, out)
553            Helper::SetProperty => &[types::I64, types::I64, types::I64, types::I64, types::I64],
554            // (frame, object, key, accessor, kind, out)
555            Helper::DefineAccessor => &[
556                types::I64,
557                types::I64,
558                types::I64,
559                types::I64,
560                types::I32,
561                types::I64,
562            ],
563            // (frame, callee, this, arguments, out)
564            Helper::Call => &[types::I64, types::I64, types::I64, types::I64, types::I64],
565            // (frame, callee, arguments, out)
566            Helper::Construct => &[types::I64, types::I64, types::I64, types::I64],
567            // (frame, a, b, out): array/object mutations over two value operands
568            Helper::ArrayPush
569            | Helper::ArrayExtend
570            | Helper::ObjectSpread
571            | Helper::SetPrototype => &[types::I64, types::I64, types::I64, types::I64],
572            // (frame, name/selector, value, out): string-constant selector then value
573            Helper::StoreGlobal | Helper::Export => {
574                &[types::I64, types::I32, types::I64, types::I64]
575            }
576            // (frame, pattern, flags, out)
577            Helper::CreateRegExp => &[types::I64, types::I32, types::I32, types::I64],
578            // (frame, src, kind, out)
579            Helper::GetIterator => &[types::I64, types::I64, types::I32, types::I64],
580            // (frame, iterator, done_reg, value_reg, out)
581            Helper::IteratorNext => &[types::I64, types::I64, types::I32, types::I32, types::I64],
582            // (frame, value)
583            Helper::Truthy => &[types::I64, types::I64],
584        }
585    }
586
587    /// The helper's Cranelift signature under `call_conv`. Every helper returns
588    /// an `i32` (a completion tag, or the `0`/`1` truth value for
589    /// [`Helper::Truthy`]).
590    fn signature(self, call_conv: CallConv) -> Signature {
591        let mut signature = Signature::new(call_conv);
592        for &ty in self.param_types() {
593            signature.params.push(AbiParam::new(ty));
594        }
595        signature.returns.push(AbiParam::new(types::I32));
596        signature
597    }
598}
599
600/// The ABI operator selector for a unary operator, passed as the `op` argument
601/// to [`Helper::Unary`]. This is the stable codegen-side operator encoding.
602const fn unary_op_selector(op: UnaryOp) -> i64 {
603    match op {
604        UnaryOp::Void => 0,
605        UnaryOp::TypeOf => 1,
606        UnaryOp::Plus => 2,
607        UnaryOp::Negate => 3,
608        UnaryOp::BitwiseNot => 4,
609        UnaryOp::LogicalNot => 5,
610    }
611}
612
613/// The ABI operator selector for a binary operator, passed as the `op` argument
614/// to [`Helper::Binary`]. This is the stable codegen-side operator encoding.
615const fn binary_op_selector(op: BinaryOp) -> i64 {
616    match op {
617        BinaryOp::Add => 0,
618        BinaryOp::Subtract => 1,
619        BinaryOp::Multiply => 2,
620        BinaryOp::Divide => 3,
621        BinaryOp::Remainder => 4,
622        BinaryOp::Exponent => 5,
623        BinaryOp::BitAnd => 6,
624        BinaryOp::BitOr => 7,
625        BinaryOp::BitXor => 8,
626        BinaryOp::ShiftLeft => 9,
627        BinaryOp::ShiftRight => 10,
628        BinaryOp::UnsignedShiftRight => 11,
629        BinaryOp::Equal => 12,
630        BinaryOp::NotEqual => 13,
631        BinaryOp::StrictEqual => 14,
632        BinaryOp::StrictNotEqual => 15,
633        BinaryOp::LessThan => 16,
634        BinaryOp::LessThanOrEqual => 17,
635        BinaryOp::GreaterThan => 18,
636        BinaryOp::GreaterThanOrEqual => 19,
637        BinaryOp::InstanceOf => 20,
638        BinaryOp::In => 21,
639    }
640}
641
642/// The ABI selector for an iterator protocol, passed as the `kind` argument to
643/// [`Helper::GetIterator`]. This is the stable codegen-side encoding.
644const fn iterator_kind_selector(kind: IteratorKind) -> i64 {
645    match kind {
646        IteratorKind::Sync => 0,
647        IteratorKind::Async => 1,
648        IteratorKind::Keys => 2,
649    }
650}
651
652/// The ABI selector for an accessor half, passed as the `kind` argument to
653/// [`Helper::DefineAccessor`]. This is the stable codegen-side encoding.
654const fn accessor_kind_selector(kind: AccessorKind) -> i64 {
655    match kind {
656        AccessorKind::Getter => 0,
657        AccessorKind::Setter => 1,
658    }
659}
660
661// -- Errors ------------------------------------------------------------------
662
663/// A deterministic, typed lowering failure.
664#[derive(Clone, Debug, Eq, PartialEq)]
665pub enum LowerError {
666    /// The target is not 64-bit.
667    UnsupportedPointerWidth {
668        /// The offending pointer width in bits.
669        bits: u8,
670    },
671    /// The module has more functions than a `u32` can index.
672    TooManyFunctions {
673        /// The offending function count.
674        count: usize,
675    },
676    /// A function's register file cannot be addressed with 32-bit slot offsets.
677    RegisterFileTooLarge {
678        /// The offending function.
679        function: FunctionId,
680        /// Its register count.
681        register_count: u32,
682    },
683    /// A lowered function's entry signature differs from the native ABI.
684    EntrySignatureMismatch {
685        /// The offending function.
686        function: FunctionId,
687    },
688    /// Cranelift's IR verifier rejected a lowered function.
689    IrVerification {
690        /// The offending function.
691        function: FunctionId,
692        /// The verifier's diagnostics.
693        message: String,
694    },
695}
696
697impl fmt::Display for LowerError {
698    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
699        match self {
700            LowerError::UnsupportedPointerWidth { bits } => {
701                write!(f, "target must be 64-bit, got {bits}-bit")
702            }
703            LowerError::TooManyFunctions { count } => {
704                write!(f, "module has {count} functions, exceeding u32 index range")
705            }
706            LowerError::RegisterFileTooLarge {
707                function,
708                register_count,
709            } => write!(
710                f,
711                "function {} register file of {register_count} slots is not 32-bit addressable",
712                function.get()
713            ),
714            LowerError::EntrySignatureMismatch { function } => write!(
715                f,
716                "function {} lowered to a non-native entry signature",
717                function.get()
718            ),
719            LowerError::IrVerification { function, message } => {
720                write!(
721                    f,
722                    "function {} failed IR verification: {message}",
723                    function.get()
724                )
725            }
726        }
727    }
728}
729
730impl Error for LowerError {}
731
732/// A deterministic lowering failure anchored to its canonical program module.
733#[derive(Clone, Debug, Eq, PartialEq)]
734pub struct ProgramLowerError {
735    /// The module whose bytecode could not be lowered.
736    pub module: ModuleId,
737    /// The module-local lowering failure.
738    pub kind: LowerError,
739}
740
741impl fmt::Display for ProgramLowerError {
742    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
743        write!(
744            f,
745            "module {} could not be lowered: {}",
746            self.module.get(),
747            self.kind
748        )
749    }
750}
751
752impl Error for ProgramLowerError {
753    fn source(&self) -> Option<&(dyn Error + 'static)> {
754        Some(&self.kind)
755    }
756}
757
758// -- Lowered records ---------------------------------------------------------
759
760/// One lowered function: its Cranelift IR plus the metadata a backend needs to
761/// compile and link it without re-deriving anything.
762#[derive(Clone)]
763pub struct LoweredFunction {
764    /// The bytecode function this lowering corresponds to.
765    pub id: FunctionId,
766    /// The linker symbol for this function.
767    pub symbol: String,
768    /// The native-entry signature `(frame, out) -> tag`.
769    pub signature: Signature,
770    /// The verified Cranelift IR.
771    pub clif: Function,
772    /// The resume-dispatch tokens the entry accepts (`0` plus each `P + 1`).
773    pub entry_points: Vec<u32>,
774    /// The runtime helpers this function imports, in a stable order.
775    pub helpers: Vec<Helper>,
776    /// The count of leading capture-cell registers the runtime seeds from a
777    /// `CreateClosure` captures array before parameters (from
778    /// [`bamts_bytecode::Function::capture_count`]). Codegen surfaces it as
779    /// metadata; the entry-init copy is a runtime concern.
780    pub capture_count: u32,
781}
782
783impl fmt::Debug for LoweredFunction {
784    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
785        f.debug_struct("LoweredFunction")
786            .field("id", &self.id)
787            .field("symbol", &self.symbol)
788            .field("entry_points", &self.entry_points)
789            .field("helpers", &self.helpers)
790            .field("capture_count", &self.capture_count)
791            .finish_non_exhaustive()
792    }
793}
794
795/// The complete lowering of one verified module within a program.
796#[derive(Clone)]
797pub struct LoweredModule {
798    /// The canonical program-local module id.
799    pub id: ModuleId,
800    /// One lowered function per bytecode function, in module-local index order.
801    pub functions: Vec<LoweredFunction>,
802    /// The module entry function.
803    pub entry: FunctionId,
804    /// The calling convention every lowered function uses.
805    pub call_conv: CallConv,
806}
807
808impl fmt::Debug for LoweredModule {
809    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
810        f.debug_struct("LoweredModule")
811            .field("id", &self.id)
812            .field("functions", &self.functions)
813            .field("entry", &self.entry)
814            .field("call_conv", &self.call_conv)
815            .finish()
816    }
817}
818
819/// The shared lowering of one canonical verified program.
820#[derive(Clone, Debug)]
821pub struct LoweredProgram {
822    /// One lowering per program module, in canonical module-id order.
823    pub modules: Vec<LoweredModule>,
824    /// The program entry module.
825    pub entry_module: ModuleId,
826    /// The entry function local to `entry_module`.
827    pub entry_function: FunctionId,
828}
829
830/// The collision-free linker symbol for a module-qualified lowered function.
831#[must_use]
832pub fn function_symbol(module_id: u32, function_id: u32) -> String {
833    format!("bamts_m{module_id}_fn_{function_id}")
834}
835
836// -- Lowering entry point ----------------------------------------------------
837
838/// Lowers every function of every module in a verified canonical program.
839///
840/// Modules remain separate: function and constant ids are never flattened or
841/// renumbered. Each error carries the module id whose lowering failed.
842pub fn lower_program(
843    program: &Program<Verified>,
844    config: TargetFrontendConfig,
845) -> Result<LoweredProgram, ProgramLowerError> {
846    let mut modules = Vec::with_capacity(program.modules().len());
847    for (index, module) in program.modules().iter().enumerate() {
848        let module_id = ModuleId::new(index as u32);
849        modules.push(
850            lower_code_module(module_id, module.code(), config).map_err(|kind| {
851                ProgramLowerError {
852                    module: module_id,
853                    kind,
854                }
855            })?,
856        );
857    }
858    let entry_module = program.entry();
859    let entry_function = program
860        .module(entry_module)
861        .expect("verified program entry module exists")
862        .code()
863        .entry();
864    Ok(LoweredProgram {
865        modules,
866        entry_module,
867        entry_function,
868    })
869}
870
871fn lower_code_module(
872    module_id: ModuleId,
873    module: &Module<Verified>,
874    config: TargetFrontendConfig,
875) -> Result<LoweredModule, LowerError> {
876    if config.pointer_bits() != 64 {
877        return Err(LowerError::UnsupportedPointerWidth {
878            bits: config.pointer_bits(),
879        });
880    }
881    let function_count = module.functions().len();
882    if u32::try_from(function_count).is_err() {
883        return Err(LowerError::TooManyFunctions {
884            count: function_count,
885        });
886    }
887
888    let call_conv = config.default_call_conv;
889    let entry_signature = entry_signature(call_conv);
890    let flags = Flags::new(settings::builder());
891    let mut builder_context = FunctionBuilderContext::new();
892
893    let mut functions = Vec::with_capacity(function_count);
894    for (index, function) in module.functions().iter().enumerate() {
895        // Bounds checked above.
896        let id = FunctionId::new(index as u32);
897        let lowered = lower_function(
898            module_id,
899            id,
900            function,
901            &entry_signature,
902            config,
903            &flags,
904            &mut builder_context,
905        )?;
906        functions.push(lowered);
907    }
908
909    Ok(LoweredModule {
910        id: module_id,
911        functions,
912        entry: module.entry(),
913        call_conv,
914    })
915}
916
917/// The shared native-entry signature: `(frame, out) -> tag`.
918fn entry_signature(call_conv: CallConv) -> Signature {
919    let mut signature = Signature::new(call_conv);
920    signature.params.push(AbiParam::new(types::I64)); // *mut ShadowFrame
921    signature.params.push(AbiParam::new(types::I64)); // *mut Completion
922    signature.returns.push(AbiParam::new(types::I32)); // CompletionTag
923    signature
924}
925
926/// Validates that a function's register file is addressable with the 32-bit
927/// slot offsets used by every frame load and store. The bytecode verifier
928/// proves register references stay within `register_count`; this proves
929/// `register_count` itself fits the CLIF addressing convention.
930fn validate_slots(id: FunctionId, function: &bamts_bytecode::Function) -> Result<(), LowerError> {
931    let register_count = function.register_count();
932    let addressable = i64::from(register_count)
933        .checked_mul(VALUE_BYTES)
934        .is_some_and(|bytes| i32::try_from(bytes).is_ok());
935    if addressable {
936        Ok(())
937    } else {
938        Err(LowerError::RegisterFileTooLarge {
939            function: id,
940            register_count,
941        })
942    }
943}
944
945fn lower_function(
946    module_id: ModuleId,
947    id: FunctionId,
948    function: &bamts_bytecode::Function,
949    entry_signature: &Signature,
950    config: TargetFrontendConfig,
951    flags: &Flags,
952    builder_context: &mut FunctionBuilderContext,
953) -> Result<LoweredFunction, LowerError> {
954    validate_slots(id, function)?;
955
956    let code = function.code();
957    let handlers = function.handlers();
958    let reachable = reachable_pcs(code, handlers);
959    let entry_points = resume_tokens(code, &reachable);
960
961    let name = UserFuncName::user(FUNCTION_NAMESPACE, id.get());
962    let mut clif = Function::with_name_signature(name, entry_signature.clone());
963
964    let helpers = {
965        let builder = FunctionBuilder::new(&mut clif, builder_context);
966        let mut lowering = Lowering::new(builder, code.len(), handlers, config.default_call_conv);
967        lowering.build(code, &reachable);
968        lowering.finish(config)
969    };
970
971    // Signature validation: the produced entry ABI must be identical to the
972    // shared native-entry signature (params and returns), independent of the
973    // structural IR checks Cranelift performs below.
974    if clif.signature != *entry_signature {
975        return Err(LowerError::EntrySignatureMismatch { function: id });
976    }
977
978    verify_function(&clif, flags).map_err(|errors| LowerError::IrVerification {
979        function: id,
980        message: errors.to_string(),
981    })?;
982
983    Ok(LoweredFunction {
984        id,
985        symbol: function_symbol(module_id.get(), id.get()),
986        signature: entry_signature.clone(),
987        clif,
988        entry_points,
989        helpers,
990        capture_count: function.capture_count(),
991    })
992}
993
994// -- Per-function lowering ---------------------------------------------------
995
996struct Lowering<'a> {
997    builder: FunctionBuilder<'a>,
998    /// One block per reachable bytecode pc; `None` for unreachable pcs, which
999    /// are never emitted (keeping every block dominated by the entry block).
1000    pc_blocks: Vec<Option<Block>>,
1001    /// The resume prologue block for each reachable `Suspend`, keyed by the
1002    /// suspend's bytecode pc.
1003    resume_blocks: BTreeMap<usize, Block>,
1004    handlers: &'a [ExceptionHandler],
1005    call_conv: CallConv,
1006    frame: Value,
1007    out: Value,
1008    helper_refs: BTreeMap<Helper, cranelift_codegen::ir::FuncRef>,
1009}
1010
1011impl<'a> Lowering<'a> {
1012    fn new(
1013        mut builder: FunctionBuilder<'a>,
1014        code_len: usize,
1015        handlers: &'a [ExceptionHandler],
1016        call_conv: CallConv,
1017    ) -> Self {
1018        let pc_blocks = vec![None; code_len];
1019        // The entry/dispatch block owns the function parameters. Reachable pc
1020        // blocks are created up front in ascending order for deterministic IR.
1021        let dispatch = builder.create_block();
1022        builder.append_block_params_for_function_params(dispatch);
1023        builder.switch_to_block(dispatch);
1024        let frame = builder.block_params(dispatch)[0];
1025        let out = builder.block_params(dispatch)[1];
1026        Self {
1027            builder,
1028            pc_blocks,
1029            resume_blocks: BTreeMap::new(),
1030            handlers,
1031            call_conv,
1032            frame,
1033            out,
1034            helper_refs: BTreeMap::new(),
1035        }
1036    }
1037
1038    fn build(&mut self, code: &[Instruction], reachable: &BTreeSet<usize>) {
1039        for &pc in reachable {
1040            self.pc_blocks[pc] = Some(self.builder.create_block());
1041        }
1042        for &pc in reachable {
1043            if let Instruction::Suspend { .. } = code[pc] {
1044                let block = self.builder.create_block();
1045                self.resume_blocks.insert(pc, block);
1046            }
1047        }
1048        self.emit_dispatch();
1049        for &pc in reachable {
1050            self.emit_instruction(pc, code[pc]);
1051        }
1052        for &pc in reachable {
1053            if let Instruction::Suspend { dst, resume, .. } = code[pc] {
1054                self.emit_resume_prologue(pc, dst, resume);
1055            }
1056        }
1057        self.builder.seal_all_blocks();
1058    }
1059
1060    /// Consumes the builder, returning the imported helpers in a stable order.
1061    fn finish(self, config: TargetFrontendConfig) -> Vec<Helper> {
1062        let helpers = self.helper_refs.keys().copied().collect();
1063        self.builder.finalize(config);
1064        helpers
1065    }
1066
1067    /// Emits the entry block: select the starting block from the resume token in
1068    /// `frame.bytecode_pc`. Token `0` is a fresh call (pc-0 block); token `P + 1`
1069    /// enters the resume prologue for the suspend at pc `P`.
1070    fn emit_dispatch(&mut self) {
1071        // A function with no suspends has a single entry (token 0): fresh calls
1072        // always begin at pc 0, so no token comparison is emitted.
1073        if self.resume_blocks.is_empty() {
1074            let target = self.pc_blocks[0].expect("entry pc is reachable");
1075            self.builder.ins().jump(target, &[]);
1076            return;
1077        }
1078
1079        let token = self.builder.ins().load(
1080            types::I32,
1081            MemFlagsData::trusted(),
1082            self.frame,
1083            SHADOW_FRAME_PC_OFFSET,
1084        );
1085        // Fresh entry (token 0).
1086        let fresh = self.pc_blocks[0].expect("entry pc is reachable");
1087        let after_fresh = self.builder.create_block();
1088        let is_fresh = self.builder.ins().icmp_imm_u(IntCC::Equal, token, 0);
1089        self.builder
1090            .ins()
1091            .brif(is_fresh, fresh, &[], after_fresh, &[]);
1092        self.builder.switch_to_block(after_fresh);
1093        // Resume tokens (P + 1), in ascending pc order for deterministic IR.
1094        let resume: Vec<(usize, Block)> = self
1095            .resume_blocks
1096            .iter()
1097            .map(|(&pc, &block)| (pc, block))
1098            .collect();
1099        for (pc, block) in resume {
1100            let token_value = i64::from(pc as u32 + 1);
1101            let matches = self
1102                .builder
1103                .ins()
1104                .icmp_imm_u(IntCC::Equal, token, token_value);
1105            let next = self.builder.create_block();
1106            self.builder.ins().brif(matches, block, &[], next, &[]);
1107            self.builder.switch_to_block(next);
1108        }
1109        // Resumed at an unrecognized token: fatal trap back to the runtime.
1110        self.emit_trap(TRAP_INVALID_RESUME);
1111    }
1112
1113    fn emit_instruction(&mut self, pc: usize, instruction: Instruction) {
1114        let block = self.pc_blocks[pc].expect("reachable pc has a block");
1115        self.builder.switch_to_block(block);
1116        let current_pc = self.iconst32(i64::from(pc as u32));
1117        self.builder.ins().store(
1118            MemFlagsData::trusted(),
1119            current_pc,
1120            self.frame,
1121            SHADOW_FRAME_PC_OFFSET,
1122        );
1123        if is_inline_instruction(instruction) {
1124            self.emit_consume_fuel();
1125        }
1126        match instruction {
1127            Instruction::LoadConst { dst, constant } => {
1128                let const_id = self.iconst32(i64::from(constant.get()));
1129                let tag = self.call_helper(Helper::LoadConstant, &[self.frame, const_id, self.out]);
1130                self.route_completion(pc, tag, Some(dst));
1131            }
1132            Instruction::Move { dst, src } => {
1133                let handles = self.load_handles();
1134                let value = self.load_register(handles, src);
1135                self.store_register(handles, dst, value);
1136                self.jump_to_next(pc);
1137            }
1138            Instruction::Unary { dst, op, operand } => {
1139                let handles = self.load_handles();
1140                let operand_value = self.load_register(handles, operand);
1141                let selector = self.iconst32(unary_op_selector(op));
1142                let tag = self.call_helper(
1143                    Helper::Unary,
1144                    &[self.frame, selector, operand_value, self.out],
1145                );
1146                self.route_completion(pc, tag, Some(dst));
1147            }
1148            Instruction::Binary {
1149                dst,
1150                op,
1151                left,
1152                right,
1153            } => {
1154                let handles = self.load_handles();
1155                let left_value = self.load_register(handles, left);
1156                let right_value = self.load_register(handles, right);
1157                let selector = self.iconst32(binary_op_selector(op));
1158                let tag = self.call_helper(
1159                    Helper::Binary,
1160                    &[self.frame, selector, left_value, right_value, self.out],
1161                );
1162                self.route_completion(pc, tag, Some(dst));
1163            }
1164            Instruction::CreateObject { dst } => {
1165                let tag = self.call_helper(Helper::CreateObject, &[self.frame, self.out]);
1166                self.route_completion(pc, tag, Some(dst));
1167            }
1168            Instruction::CreateArray { dst } => {
1169                let tag = self.call_helper(Helper::CreateArray, &[self.frame, self.out]);
1170                self.route_completion(pc, tag, Some(dst));
1171            }
1172            Instruction::CreateCell { dst } => {
1173                let tag = self.call_helper(Helper::CreateCell, &[self.frame, self.out]);
1174                self.route_completion(pc, tag, Some(dst));
1175            }
1176            Instruction::CreateClosure {
1177                dst,
1178                function,
1179                captures,
1180            } => {
1181                let handles = self.load_handles();
1182                let captures_value = self.load_register(handles, captures);
1183                let function_id = self.iconst32(i64::from(function.get()));
1184                let tag = self.call_helper(
1185                    Helper::CreateClosure,
1186                    &[self.frame, function_id, captures_value, self.out],
1187                );
1188                self.route_completion(pc, tag, Some(dst));
1189            }
1190            Instruction::GetProperty { dst, object, key } => {
1191                let handles = self.load_handles();
1192                let object_value = self.load_register(handles, object);
1193                let key_value = self.load_register(handles, key);
1194                let tag = self.call_helper(
1195                    Helper::GetProperty,
1196                    &[self.frame, object_value, key_value, self.out],
1197                );
1198                self.route_completion(pc, tag, Some(dst));
1199            }
1200            Instruction::SetProperty { object, key, value } => {
1201                let handles = self.load_handles();
1202                let object_value = self.load_register(handles, object);
1203                let key_value = self.load_register(handles, key);
1204                let value_value = self.load_register(handles, value);
1205                let tag = self.call_helper(
1206                    Helper::SetProperty,
1207                    &[self.frame, object_value, key_value, value_value, self.out],
1208                );
1209                self.route_completion(pc, tag, None);
1210            }
1211            Instruction::DeleteProperty { dst, object, key } => {
1212                let handles = self.load_handles();
1213                let object_value = self.load_register(handles, object);
1214                let key_value = self.load_register(handles, key);
1215                let tag = self.call_helper(
1216                    Helper::DeleteProperty,
1217                    &[self.frame, object_value, key_value, self.out],
1218                );
1219                self.route_completion(pc, tag, Some(dst));
1220            }
1221            Instruction::DefineAccessor {
1222                object,
1223                key,
1224                accessor,
1225                kind,
1226            } => {
1227                let handles = self.load_handles();
1228                let object_value = self.load_register(handles, object);
1229                let key_value = self.load_register(handles, key);
1230                let accessor_value = self.load_register(handles, accessor);
1231                let selector = self.iconst32(accessor_kind_selector(kind));
1232                let tag = self.call_helper(
1233                    Helper::DefineAccessor,
1234                    &[
1235                        self.frame,
1236                        object_value,
1237                        key_value,
1238                        accessor_value,
1239                        selector,
1240                        self.out,
1241                    ],
1242                );
1243                self.route_completion(pc, tag, None);
1244            }
1245            Instruction::Call {
1246                dst,
1247                callee,
1248                this_value,
1249                arguments,
1250            } => {
1251                let handles = self.load_handles();
1252                let callee_value = self.load_register(handles, callee);
1253                let this = self.load_register(handles, this_value);
1254                let args = self.load_register(handles, arguments);
1255                let tag = self.call_helper(
1256                    Helper::Call,
1257                    &[self.frame, callee_value, this, args, self.out],
1258                );
1259                self.route_completion(pc, tag, Some(dst));
1260            }
1261            Instruction::Construct {
1262                dst,
1263                callee,
1264                arguments,
1265            } => {
1266                let handles = self.load_handles();
1267                let callee_value = self.load_register(handles, callee);
1268                let args = self.load_register(handles, arguments);
1269                let tag = self.call_helper(
1270                    Helper::Construct,
1271                    &[self.frame, callee_value, args, self.out],
1272                );
1273                self.route_completion(pc, tag, Some(dst));
1274            }
1275            Instruction::LoadGlobal { dst, name } => {
1276                let name_id = self.iconst32(i64::from(name.get()));
1277                let tag = self.call_helper(Helper::LoadGlobal, &[self.frame, name_id, self.out]);
1278                self.route_completion(pc, tag, Some(dst));
1279            }
1280            Instruction::StoreGlobal { name, value } => {
1281                let handles = self.load_handles();
1282                let value_value = self.load_register(handles, value);
1283                let name_id = self.iconst32(i64::from(name.get()));
1284                let tag = self.call_helper(
1285                    Helper::StoreGlobal,
1286                    &[self.frame, name_id, value_value, self.out],
1287                );
1288                self.route_completion(pc, tag, None);
1289            }
1290            Instruction::TypeOfGlobal { dst, name } => {
1291                let name_id = self.iconst32(i64::from(name.get()));
1292                let tag = self.call_helper(Helper::TypeOfGlobal, &[self.frame, name_id, self.out]);
1293                self.route_completion(pc, tag, Some(dst));
1294            }
1295            Instruction::LoadThis { dst } => {
1296                let tag = self.call_helper(Helper::LoadThis, &[self.frame, self.out]);
1297                self.route_completion(pc, tag, Some(dst));
1298            }
1299            Instruction::LoadArguments { dst } => {
1300                let tag = self.call_helper(Helper::LoadArguments, &[self.frame, self.out]);
1301                self.route_completion(pc, tag, Some(dst));
1302            }
1303            Instruction::LoadNewTarget { dst } => {
1304                let tag = self.call_helper(Helper::LoadNewTarget, &[self.frame, self.out]);
1305                self.route_completion(pc, tag, Some(dst));
1306            }
1307            Instruction::ArrayPush { array, value } => {
1308                let handles = self.load_handles();
1309                let array_value = self.load_register(handles, array);
1310                let value_value = self.load_register(handles, value);
1311                let tag = self.call_helper(
1312                    Helper::ArrayPush,
1313                    &[self.frame, array_value, value_value, self.out],
1314                );
1315                self.route_completion(pc, tag, None);
1316            }
1317            Instruction::ArrayExtend { array, iterable } => {
1318                let handles = self.load_handles();
1319                let array_value = self.load_register(handles, array);
1320                let iterable_value = self.load_register(handles, iterable);
1321                let tag = self.call_helper(
1322                    Helper::ArrayExtend,
1323                    &[self.frame, array_value, iterable_value, self.out],
1324                );
1325                self.route_completion(pc, tag, None);
1326            }
1327            Instruction::ObjectSpread { target, source } => {
1328                let handles = self.load_handles();
1329                let target_value = self.load_register(handles, target);
1330                let source_value = self.load_register(handles, source);
1331                let tag = self.call_helper(
1332                    Helper::ObjectSpread,
1333                    &[self.frame, target_value, source_value, self.out],
1334                );
1335                self.route_completion(pc, tag, None);
1336            }
1337            Instruction::SetPrototype { object, prototype } => {
1338                let handles = self.load_handles();
1339                let object_value = self.load_register(handles, object);
1340                let prototype_value = self.load_register(handles, prototype);
1341                let tag = self.call_helper(
1342                    Helper::SetPrototype,
1343                    &[self.frame, object_value, prototype_value, self.out],
1344                );
1345                self.route_completion(pc, tag, None);
1346            }
1347            Instruction::CreatePrivateName { dst, description } => {
1348                let description_id = self.iconst32(i64::from(description.get()));
1349                let tag = self.call_helper(
1350                    Helper::CreatePrivateName,
1351                    &[self.frame, description_id, self.out],
1352                );
1353                self.route_completion(pc, tag, Some(dst));
1354            }
1355            Instruction::CreateRegExp {
1356                dst,
1357                pattern,
1358                flags,
1359            } => {
1360                let pattern_id = self.iconst32(i64::from(pattern.get()));
1361                let flags_id = self.iconst32(i64::from(flags.get()));
1362                let tag = self.call_helper(
1363                    Helper::CreateRegExp,
1364                    &[self.frame, pattern_id, flags_id, self.out],
1365                );
1366                self.route_completion(pc, tag, Some(dst));
1367            }
1368            Instruction::GetIterator { dst, src, kind } => {
1369                let handles = self.load_handles();
1370                let src_value = self.load_register(handles, src);
1371                let selector = self.iconst32(iterator_kind_selector(kind));
1372                let tag = self.call_helper(
1373                    Helper::GetIterator,
1374                    &[self.frame, src_value, selector, self.out],
1375                );
1376                self.route_completion(pc, tag, Some(dst));
1377            }
1378            Instruction::IteratorNext {
1379                done,
1380                value,
1381                iterator,
1382            } => {
1383                let handles = self.load_handles();
1384                let iterator_value = self.load_register(handles, iterator);
1385                let done_reg = self.iconst32(i64::from(done.get()));
1386                let value_reg = self.iconst32(i64::from(value.get()));
1387                // Two-write: the helper writes both `done` and `value` slots
1388                // directly from the frame on Normal, so no `dst` store here.
1389                let tag = self.call_helper(
1390                    Helper::IteratorNext,
1391                    &[self.frame, iterator_value, done_reg, value_reg, self.out],
1392                );
1393                self.route_completion(pc, tag, None);
1394            }
1395            Instruction::Import { dst, specifier } => {
1396                let specifier_id = self.iconst32(i64::from(specifier.get()));
1397                let tag = self.call_helper(Helper::Import, &[self.frame, specifier_id, self.out]);
1398                self.route_completion(pc, tag, Some(dst));
1399            }
1400            Instruction::Export { name, src } => {
1401                let handles = self.load_handles();
1402                let src_value = self.load_register(handles, src);
1403                let name_id = self.iconst32(i64::from(name.get()));
1404                let tag =
1405                    self.call_helper(Helper::Export, &[self.frame, name_id, src_value, self.out]);
1406                self.route_completion(pc, tag, None);
1407            }
1408            Instruction::Jump { target } => {
1409                let target = self.pc_block(target);
1410                self.builder.ins().jump(target, &[]);
1411            }
1412            Instruction::JumpIfTrue { condition, target } => {
1413                self.emit_conditional(condition, target.get() as usize, pc + 1);
1414            }
1415            Instruction::JumpIfFalse { condition, target } => {
1416                self.emit_conditional(condition, pc + 1, target.get() as usize);
1417            }
1418            Instruction::Return { value } => self.emit_return(value),
1419            Instruction::Throw { value } => self.emit_throw(pc, value),
1420            Instruction::Suspend { src, .. } => self.emit_suspend(pc, src),
1421            Instruction::Halt => self.emit_halt(),
1422        }
1423    }
1424
1425    /// Routes a value-helper completion: on `Normal`, store `out.value` into
1426    /// `dst` (when the opcode defines one) and continue; otherwise route the
1427    /// abnormal completion to a handler or the caller.
1428    fn route_completion(&mut self, pc: usize, tag: Value, dst: Option<Register>) {
1429        let normal = self.builder.create_block();
1430        let abnormal = self.builder.create_block();
1431        // Normal (tag == 0) takes `normal`; any nonzero tag is abnormal.
1432        self.builder.ins().brif(tag, abnormal, &[], normal, &[]);
1433
1434        self.builder.switch_to_block(normal);
1435        if let Some(dst) = dst {
1436            let handles = self.load_handles();
1437            let result = self.load_completion_value();
1438            self.store_register(handles, dst, result);
1439        }
1440        self.jump_to_next(pc);
1441
1442        self.builder.switch_to_block(abnormal);
1443        self.emit_abnormal_completion(pc, tag);
1444    }
1445
1446    /// Routes a nonzero completion tag: to a covering handler on `Throw` (the
1447    /// thrown value is bound into the handler's `catch_register`), otherwise
1448    /// propagated to the caller. `FatalTrap` never enters a handler.
1449    ///
1450    /// If the covering handler's block was not emitted (only a total,
1451    /// non-throwing helper reaches it), the completion is propagated to the
1452    /// caller — this abnormal edge is provably unreachable for such helpers.
1453    fn emit_abnormal_completion(&mut self, pc: usize, tag: Value) {
1454        let covering = innermost_handler(self.handlers, pc).and_then(|handler| {
1455            self.emitted_handler_block(handler)
1456                .map(|block| (handler, block))
1457        });
1458        match covering {
1459            Some((handler, handler_block)) => {
1460                let bind = self.builder.create_block();
1461                let propagate = self.builder.create_block();
1462                let is_throw = self.builder.ins().icmp_imm_u(IntCC::Equal, tag, TAG_THROW);
1463                self.builder.ins().brif(is_throw, bind, &[], propagate, &[]);
1464
1465                self.builder.switch_to_block(bind);
1466                let handles = self.load_handles();
1467                let thrown = self.load_completion_value();
1468                self.store_register(handles, handler.catch_register, thrown);
1469                self.builder.ins().jump(handler_block, &[]);
1470
1471                self.builder.switch_to_block(propagate);
1472                self.builder.ins().return_(&[tag]);
1473            }
1474            None => {
1475                self.builder.ins().return_(&[tag]);
1476            }
1477        }
1478    }
1479
1480    /// The emitted block for a handler's target pc, or `None` when that pc was
1481    /// not marked reachable (so no throwing opcode routes there).
1482    fn emitted_handler_block(&self, handler: ExceptionHandler) -> Option<Block> {
1483        self.pc_blocks[handler.handler.get() as usize]
1484    }
1485
1486    /// Emits `JumpIfTrue`/`JumpIfFalse`: coerce `condition` to boolean via the
1487    /// total [`Helper::Truthy`], then branch to `true_target` on truthy and
1488    /// `false_target` otherwise.
1489    fn emit_conditional(&mut self, condition: Register, true_target: usize, false_target: usize) {
1490        let handles = self.load_handles();
1491        let condition_value = self.load_register(handles, condition);
1492        let truth = self.call_helper(Helper::Truthy, &[self.frame, condition_value]);
1493        let then_block = self.pc_blocks[true_target].expect("branch target is reachable");
1494        let else_block = self.pc_blocks[false_target].expect("branch target is reachable");
1495        self.builder
1496            .ins()
1497            .brif(truth, then_block, &[], else_block, &[]);
1498    }
1499
1500    fn emit_return(&mut self, value: Register) {
1501        let handles = self.load_handles();
1502        let return_value = self.load_register(handles, value);
1503        self.store_completion_value(return_value);
1504        let tag = self.iconst32(TAG_NORMAL);
1505        self.builder.ins().return_(&[tag]);
1506    }
1507
1508    /// Emits `Throw`: bind the thrown value into a covering handler's
1509    /// `catch_register` and branch there, or write it to `out.value` and return
1510    /// `Throw` to the caller.
1511    fn emit_throw(&mut self, pc: usize, value: Register) {
1512        let handles = self.load_handles();
1513        let thrown = self.load_register(handles, value);
1514        match innermost_handler(self.handlers, pc) {
1515            Some(handler) => {
1516                self.store_register(handles, handler.catch_register, thrown);
1517                let handler_block = self.pc_block(handler.handler);
1518                self.builder.ins().jump(handler_block, &[]);
1519            }
1520            None => {
1521                self.store_completion_value(thrown);
1522                let tag = self.iconst32(TAG_THROW);
1523                self.builder.ins().return_(&[tag]);
1524            }
1525        }
1526    }
1527
1528    /// Emits the `Suspend` yield path: store this suspend's resume token into
1529    /// `frame.bytecode_pc`, yield `src` in `out.value`, and return `Suspend`.
1530    fn emit_suspend(&mut self, pc: usize, src: Register) {
1531        let token = self.iconst32(i64::from(pc as u32 + 1));
1532        self.builder.ins().store(
1533            MemFlagsData::trusted(),
1534            token,
1535            self.frame,
1536            SHADOW_FRAME_PC_OFFSET,
1537        );
1538        let handles = self.load_handles();
1539        let yielded = self.load_register(handles, src);
1540        self.store_completion_value(yielded);
1541        let tag = self.iconst32(TAG_SUSPEND);
1542        self.builder.ins().return_(&[tag]);
1543    }
1544
1545    /// Emits a `Suspend` resume prologue: obtain the resumed value from the
1546    /// runtime via [`Helper::ResumeValue`], store it into `dst`, and continue at
1547    /// `resume`. A `Throw` from the resume (e.g. `generator.throw`) routes to a
1548    /// covering handler; `FatalTrap` propagates.
1549    fn emit_resume_prologue(&mut self, pc: usize, dst: Register, resume: Pc) {
1550        let block = self.resume_blocks[&pc];
1551        self.builder.switch_to_block(block);
1552        let current_pc = self.iconst32(i64::from(pc as u32));
1553        self.builder.ins().store(
1554            MemFlagsData::trusted(),
1555            current_pc,
1556            self.frame,
1557            SHADOW_FRAME_PC_OFFSET,
1558        );
1559        let tag = self.call_helper(Helper::ResumeValue, &[self.frame, self.out]);
1560
1561        let normal = self.builder.create_block();
1562        let abnormal = self.builder.create_block();
1563        self.builder.ins().brif(tag, abnormal, &[], normal, &[]);
1564
1565        self.builder.switch_to_block(normal);
1566        let handles = self.load_handles();
1567        let resumed = self.load_completion_value();
1568        self.store_register(handles, dst, resumed);
1569        let target = self.pc_block(resume);
1570        self.builder.ins().jump(target, &[]);
1571
1572        self.builder.switch_to_block(abnormal);
1573        self.emit_abnormal_completion(pc, tag);
1574    }
1575
1576    fn emit_halt(&mut self) {
1577        let undefined = self.builder.ins().iconst(types::I64, UNDEFINED_BITS);
1578        self.store_completion_value(undefined);
1579        let tag = self.iconst32(TAG_NORMAL);
1580        self.builder.ins().return_(&[tag]);
1581    }
1582
1583    fn emit_trap(&mut self, trap_id: i64) {
1584        let value = self.builder.ins().iconst(types::I64, trap_id);
1585        self.store_completion_value(value);
1586        let tag = self.iconst32(TAG_FATAL_TRAP);
1587        self.builder.ins().return_(&[tag]);
1588    }
1589
1590    fn jump_to_next(&mut self, pc: usize) {
1591        let next = self.pc_blocks[pc + 1].expect("fallthrough successor is reachable");
1592        self.builder.ins().jump(next, &[]);
1593    }
1594
1595    /// The Cranelift block for a bytecode target pc.
1596    fn pc_block(&self, target: Pc) -> Block {
1597        self.pc_blocks[target.get() as usize].expect("control-flow target is reachable")
1598    }
1599
1600    fn iconst32(&mut self, value: i64) -> Value {
1601        self.builder.ins().iconst(types::I32, value)
1602    }
1603
1604    fn load_handles(&mut self) -> Value {
1605        // Re-read on each use: a helper call may relocate the register array.
1606        self.builder.ins().load(
1607            types::I64,
1608            MemFlagsData::trusted(),
1609            self.frame,
1610            SHADOW_FRAME_HANDLES_OFFSET,
1611        )
1612    }
1613
1614    fn load_register(&mut self, handles: Value, register: Register) -> Value {
1615        self.builder.ins().load(
1616            types::I64,
1617            MemFlagsData::trusted(),
1618            handles,
1619            register_offset(register),
1620        )
1621    }
1622
1623    fn store_register(&mut self, handles: Value, register: Register, value: Value) {
1624        self.builder.ins().store(
1625            MemFlagsData::trusted(),
1626            value,
1627            handles,
1628            register_offset(register),
1629        );
1630    }
1631
1632    fn load_completion_value(&mut self) -> Value {
1633        self.builder.ins().load(
1634            types::I64,
1635            MemFlagsData::trusted(),
1636            self.out,
1637            COMPLETION_VALUE_OFFSET,
1638        )
1639    }
1640
1641    fn store_completion_value(&mut self, value: Value) {
1642        self.builder.ins().store(
1643            MemFlagsData::trusted(),
1644            value,
1645            self.out,
1646            COMPLETION_VALUE_OFFSET,
1647        );
1648    }
1649
1650    fn call_helper(&mut self, helper: Helper, args: &[Value]) -> Value {
1651        let func_ref = self.helper_ref(helper);
1652        let call = self.builder.ins().call(func_ref, args);
1653        self.builder.inst_results(call)[0]
1654    }
1655
1656    fn helper_ref(&mut self, helper: Helper) -> cranelift_codegen::ir::FuncRef {
1657        if let Some(&func_ref) = self.helper_refs.get(&helper) {
1658            return func_ref;
1659        }
1660        let signature = helper.signature(self.call_conv);
1661        let sig_ref = self.builder.import_signature(signature);
1662        let name = self
1663            .builder
1664            .func
1665            .declare_imported_user_function(UserExternalName::new(
1666                HELPER_NAMESPACE,
1667                helper.external_index(),
1668            ));
1669        let func_ref = self.builder.import_function(ExtFuncData {
1670            name: ExternalName::user(name),
1671            signature: sig_ref,
1672            colocated: false,
1673            patchable: false,
1674        });
1675        self.helper_refs.insert(helper, func_ref);
1676        func_ref
1677    }
1678
1679    fn emit_consume_fuel(&mut self) {
1680        let amount = self.iconst32(1);
1681        let tag = self.call_helper(Helper::ConsumeFuel, &[self.frame, amount, self.out]);
1682        let normal = self.builder.create_block();
1683        let abnormal = self.builder.create_block();
1684        self.builder.ins().brif(tag, abnormal, &[], normal, &[]);
1685
1686        self.builder.switch_to_block(abnormal);
1687        self.builder.ins().return_(&[tag]);
1688
1689        self.builder.switch_to_block(normal);
1690    }
1691}
1692
1693/// The byte offset of a register slot within the handles array. [`validate_slots`]
1694/// proves `register_count * 8` fits `i32`, so this conversion never truncates.
1695/// A slot offset is a full 32-bit displacement, so register ids past 127 scale
1696/// linearly without any special path.
1697fn register_offset(register: Register) -> i32 {
1698    i32::try_from(i64::from(register.get()) * VALUE_BYTES).expect("register slot offset fits i32")
1699}
1700
1701// -- CFG analysis ------------------------------------------------------------
1702
1703/// The set of pcs reachable from a fresh call (pc 0) plus every resumable
1704/// suspend point, following fallthrough, jumps, conditional targets, suspend
1705/// resumes, and the handler edge of any instruction that can route a `Throw`
1706/// completion into a covering handler.
1707fn reachable_pcs(code: &[Instruction], handlers: &[ExceptionHandler]) -> BTreeSet<usize> {
1708    let mut reachable = BTreeSet::new();
1709    let mut worklist = Vec::new();
1710    if !code.is_empty() {
1711        worklist.push(0usize);
1712    }
1713    while let Some(pc) = worklist.pop() {
1714        if !reachable.insert(pc) {
1715            continue;
1716        }
1717        let instruction = code[pc];
1718        instruction.visit_normal_successors(pc, |target| worklist.push(target));
1719        if routes_to_handler(instruction)
1720            && let Some(handler) = innermost_handler(handlers, pc)
1721        {
1722            worklist.push(handler.handler.get() as usize);
1723        }
1724    }
1725    reachable
1726}
1727
1728/// Whether an instruction can transfer control to a covering handler: the
1729/// explicit `Throw`, or any completion-helper opcode whose abnormal path may
1730/// return `Throw`. Keeping this exhaustive guarantees every handler block a
1731/// throwing opcode targets is marked reachable and thus emitted.
1732///
1733/// The `false` arm lists every opcode that provably cannot route a `Throw`:
1734/// pure control flow, `Move`, and the total helpers ([`Helper::TypeOfGlobal`],
1735/// [`Helper::LoadThis`], [`Helper::LoadArguments`], [`Helper::LoadNewTarget`],
1736/// [`Helper::CreatePrivateName`]). Using an exhaustive `match` — not
1737/// `matches!` — forces a compile error if a new opcode is left unclassified.
1738fn routes_to_handler(instruction: Instruction) -> bool {
1739    match instruction {
1740        Instruction::LoadConst { .. }
1741        | Instruction::Unary { .. }
1742        | Instruction::Binary { .. }
1743        | Instruction::CreateObject { .. }
1744        | Instruction::CreateArray { .. }
1745        | Instruction::CreateCell { .. }
1746        | Instruction::CreateClosure { .. }
1747        | Instruction::GetProperty { .. }
1748        | Instruction::SetProperty { .. }
1749        | Instruction::DeleteProperty { .. }
1750        | Instruction::DefineAccessor { .. }
1751        | Instruction::Call { .. }
1752        | Instruction::Construct { .. }
1753        | Instruction::LoadGlobal { .. }
1754        | Instruction::StoreGlobal { .. }
1755        | Instruction::ArrayPush { .. }
1756        | Instruction::ArrayExtend { .. }
1757        | Instruction::ObjectSpread { .. }
1758        | Instruction::SetPrototype { .. }
1759        | Instruction::CreateRegExp { .. }
1760        | Instruction::GetIterator { .. }
1761        | Instruction::IteratorNext { .. }
1762        | Instruction::Import { .. }
1763        | Instruction::Export { .. }
1764        | Instruction::Suspend { .. }
1765        | Instruction::Throw { .. } => true,
1766        Instruction::Move { .. }
1767        | Instruction::TypeOfGlobal { .. }
1768        | Instruction::LoadThis { .. }
1769        | Instruction::LoadArguments { .. }
1770        | Instruction::LoadNewTarget { .. }
1771        | Instruction::CreatePrivateName { .. }
1772        | Instruction::Jump { .. }
1773        | Instruction::JumpIfTrue { .. }
1774        | Instruction::JumpIfFalse { .. }
1775        | Instruction::Return { .. }
1776        | Instruction::Halt => false,
1777    }
1778}
1779
1780/// Whether the opcode is emitted directly by the compiler/reference driver and
1781/// therefore requires an explicit pre-effect fuel charge. This exhaustive match
1782/// keeps the one-charge ledger synchronized with the bytecode algebra.
1783fn is_inline_instruction(instruction: Instruction) -> bool {
1784    match instruction {
1785        Instruction::Move { .. }
1786        | Instruction::Jump { .. }
1787        | Instruction::JumpIfTrue { .. }
1788        | Instruction::JumpIfFalse { .. }
1789        | Instruction::Return { .. }
1790        | Instruction::Halt
1791        | Instruction::Throw { .. }
1792        | Instruction::Suspend { .. } => true,
1793        Instruction::LoadConst { .. }
1794        | Instruction::Unary { .. }
1795        | Instruction::Binary { .. }
1796        | Instruction::CreateObject { .. }
1797        | Instruction::CreateArray { .. }
1798        | Instruction::CreateCell { .. }
1799        | Instruction::CreateClosure { .. }
1800        | Instruction::GetProperty { .. }
1801        | Instruction::SetProperty { .. }
1802        | Instruction::DeleteProperty { .. }
1803        | Instruction::DefineAccessor { .. }
1804        | Instruction::Call { .. }
1805        | Instruction::Construct { .. }
1806        | Instruction::LoadGlobal { .. }
1807        | Instruction::StoreGlobal { .. }
1808        | Instruction::TypeOfGlobal { .. }
1809        | Instruction::LoadThis { .. }
1810        | Instruction::LoadArguments { .. }
1811        | Instruction::LoadNewTarget { .. }
1812        | Instruction::ArrayPush { .. }
1813        | Instruction::ArrayExtend { .. }
1814        | Instruction::ObjectSpread { .. }
1815        | Instruction::SetPrototype { .. }
1816        | Instruction::CreatePrivateName { .. }
1817        | Instruction::CreateRegExp { .. }
1818        | Instruction::GetIterator { .. }
1819        | Instruction::IteratorNext { .. }
1820        | Instruction::Import { .. }
1821        | Instruction::Export { .. } => false,
1822    }
1823}
1824
1825/// The resume-dispatch tokens the entry accepts: `0` (fresh call) plus `P + 1`
1826/// for each reachable `Suspend` at bytecode pc `P`, sorted and deduplicated.
1827fn resume_tokens(code: &[Instruction], reachable: &BTreeSet<usize>) -> Vec<u32> {
1828    let mut tokens = BTreeSet::new();
1829    if !code.is_empty() {
1830        tokens.insert(0u32);
1831    }
1832    for &pc in reachable {
1833        if let Instruction::Suspend { .. } = code[pc] {
1834            tokens.insert(pc as u32 + 1);
1835        }
1836    }
1837    tokens.into_iter().collect()
1838}
1839
1840/// The innermost handler whose half-open `[start, end)` interval covers `pc`.
1841///
1842/// The verifier proves handlers are pairwise nested or disjoint, so "innermost"
1843/// is the covering handler with the greatest start (ties broken by smallest end,
1844/// then latest index) — a total, deterministic order.
1845fn innermost_handler(handlers: &[ExceptionHandler], pc: usize) -> Option<ExceptionHandler> {
1846    let pc = pc as u32;
1847    let mut best: Option<(usize, ExceptionHandler)> = None;
1848    for (index, handler) in handlers.iter().copied().enumerate() {
1849        if handler.start.get() > pc || pc >= handler.end.get() {
1850            continue;
1851        }
1852        let is_better = match best {
1853            None => true,
1854            Some((best_index, current)) => {
1855                (handler.start.get(), current.end.get(), index)
1856                    > (current.start.get(), handler.end.get(), best_index)
1857            }
1858        };
1859        if is_better {
1860            best = Some((index, handler));
1861        }
1862    }
1863    best.map(|(_, handler)| handler)
1864}
1865
1866trait NormalSuccessors {
1867    /// Visits each normal-control successor pc (excluding handler edges).
1868    fn visit_normal_successors(self, pc: usize, visit: impl FnMut(usize));
1869}
1870
1871impl NormalSuccessors for Instruction {
1872    fn visit_normal_successors(self, pc: usize, mut visit: impl FnMut(usize)) {
1873        match self {
1874            Instruction::Jump { target } => visit(target.get() as usize),
1875            Instruction::JumpIfTrue { target, .. } | Instruction::JumpIfFalse { target, .. } => {
1876                visit(target.get() as usize);
1877                visit(pc + 1);
1878            }
1879            // A suspend returns now; its `resume` pc is entered by a later call
1880            // through the resume prologue.
1881            Instruction::Suspend { resume, .. } => visit(resume.get() as usize),
1882            Instruction::Return { .. } | Instruction::Throw { .. } | Instruction::Halt => {}
1883            Instruction::LoadConst { .. }
1884            | Instruction::Move { .. }
1885            | Instruction::Unary { .. }
1886            | Instruction::Binary { .. }
1887            | Instruction::CreateObject { .. }
1888            | Instruction::CreateArray { .. }
1889            | Instruction::CreateCell { .. }
1890            | Instruction::CreateClosure { .. }
1891            | Instruction::GetProperty { .. }
1892            | Instruction::SetProperty { .. }
1893            | Instruction::DeleteProperty { .. }
1894            | Instruction::DefineAccessor { .. }
1895            | Instruction::Call { .. }
1896            | Instruction::Construct { .. }
1897            | Instruction::LoadGlobal { .. }
1898            | Instruction::StoreGlobal { .. }
1899            | Instruction::TypeOfGlobal { .. }
1900            | Instruction::LoadThis { .. }
1901            | Instruction::LoadArguments { .. }
1902            | Instruction::LoadNewTarget { .. }
1903            | Instruction::ArrayPush { .. }
1904            | Instruction::ArrayExtend { .. }
1905            | Instruction::ObjectSpread { .. }
1906            | Instruction::SetPrototype { .. }
1907            | Instruction::CreatePrivateName { .. }
1908            | Instruction::CreateRegExp { .. }
1909            | Instruction::GetIterator { .. }
1910            | Instruction::IteratorNext { .. }
1911            | Instruction::Import { .. }
1912            | Instruction::Export { .. } => visit(pc + 1),
1913        }
1914    }
1915}
1916
1917#[cfg(test)]
1918mod tests {
1919    use super::*;
1920    use bamts_bytecode::{
1921        Constant, ConstantId, EcmaString, Function as BytecodeFunction, FunctionFlags, Instruction,
1922        Pc, Register,
1923    };
1924    use cranelift_codegen::isa;
1925
1926    /// A frontend config for the host target, without naming `target_lexicon`.
1927    fn host_config() -> TargetFrontendConfig {
1928        let flags = Flags::new(settings::builder());
1929        for name in [
1930            "x86_64",
1931            "aarch64",
1932            "riscv64",
1933            "s390x",
1934            "x86_64-unknown-linux-gnu",
1935        ] {
1936            if let Ok(builder) = isa::lookup_by_name(name)
1937                && let Ok(target) = builder.finish(flags.clone())
1938            {
1939                return target.frontend_config();
1940            }
1941        }
1942        panic!("no native ISA available for tests");
1943    }
1944
1945    fn reg(index: u32) -> Register {
1946        Register::new(index)
1947    }
1948
1949    fn func(
1950        register_count: u32,
1951        code: Vec<Instruction>,
1952        handlers: Vec<ExceptionHandler>,
1953    ) -> BytecodeFunction {
1954        BytecodeFunction::new(
1955            None,
1956            0,
1957            0,
1958            register_count,
1959            FunctionFlags::default(),
1960            code,
1961            handlers,
1962        )
1963    }
1964
1965    fn verified(constants: Vec<Constant>, functions: Vec<BytecodeFunction>) -> Module<Verified> {
1966        Module::new(constants, functions, FunctionId::new(0))
1967            .verify()
1968            .expect("test module verifies")
1969    }
1970
1971    fn single(function: BytecodeFunction) -> Module<Verified> {
1972        verified(vec![Constant::Undefined], vec![function])
1973    }
1974
1975    #[test]
1976    fn capture_count_metadata_is_surfaced() {
1977        // A function declaring leading capture cells surfaces that count as
1978        // lowering metadata so a backend/runtime need not re-derive it. Entry
1979        // init (capture cells then parameters) counts as definitely-initialized,
1980        // so the body may read its capture registers without a prior write.
1981        let function = BytecodeFunction::new(
1982            None,
1983            2, // capture_count
1984            1, // parameter_count
1985            4, // register_count (>= captures + params)
1986            FunctionFlags::default(),
1987            vec![
1988                Instruction::Move {
1989                    dst: reg(3),
1990                    src: reg(0), // a capture cell, initialized on entry
1991                },
1992                Instruction::Halt,
1993            ],
1994            Vec::new(),
1995        );
1996        let module = single(function);
1997        let lowered = lower_code_module(ModuleId::new(0), &module, host_config()).expect("lowers");
1998        assert_eq!(lowered.functions[0].capture_count, 2);
1999    }
2000
2001    /// Load `Undefined` (constant 0) into `dst`; a cheap way to satisfy the
2002    /// definite-initialization verifier before a register is read.
2003    fn load_undef(dst: Register) -> Instruction {
2004        Instruction::LoadConst {
2005            dst,
2006            constant: ConstantId::new(0),
2007        }
2008    }
2009
2010    fn clif_of(module: &Module<Verified>) -> String {
2011        let lowered = lower_code_module(ModuleId::new(0), module, host_config()).expect("lowers");
2012        lowered.functions[0].clif.display().to_string()
2013    }
2014
2015    /// Lower a module and return the single function's helpers and CLIF text.
2016    fn lower_one(module: &Module<Verified>) -> (Vec<Helper>, String) {
2017        let lowered = lower_code_module(ModuleId::new(0), module, host_config()).expect("lowers");
2018        let function = &lowered.functions[0];
2019        (
2020            function.helpers.clone(),
2021            function.clif.display().to_string(),
2022        )
2023    }
2024
2025    #[test]
2026    fn entry_signature_is_the_native_abi() {
2027        let module = single(func(1, vec![Instruction::Halt], Vec::new()));
2028        let lowered = lower_code_module(ModuleId::new(0), &module, host_config()).expect("lowers");
2029        let function = &lowered.functions[0];
2030        let signature = &function.signature;
2031        assert_eq!(signature.params.len(), 2);
2032        assert_eq!(signature.params[0].value_type, types::I64);
2033        assert_eq!(signature.params[1].value_type, types::I64);
2034        assert_eq!(signature.returns.len(), 1);
2035        assert_eq!(signature.returns[0].value_type, types::I32);
2036        assert_eq!(function.symbol, "bamts_m0_fn_0");
2037        assert_eq!(function.id.get(), 0);
2038        assert_eq!(lowered.entry.get(), 0);
2039    }
2040
2041    #[test]
2042    fn halt_only_function_returns_normal_with_undefined() {
2043        let module = single(func(1, vec![Instruction::Halt], Vec::new()));
2044        let clif = clif_of(&module);
2045        // Single entry (token 0): no resume-token load.
2046        assert!(
2047            !clif.contains("load.i32"),
2048            "no dispatch load expected:\n{clif}"
2049        );
2050        assert!(
2051            clif.contains("0x7ffb_0000_0000_0000"),
2052            "undefined store missing:\n{clif}"
2053        );
2054        assert!(clif.contains("return"), "must return:\n{clif}");
2055        let lowered = lower_code_module(ModuleId::new(0), &module, host_config()).expect("lowers");
2056        assert_eq!(lowered.functions[0].helpers, vec![Helper::ConsumeFuel]);
2057        assert_eq!(lowered.functions[0].entry_points, vec![0]);
2058    }
2059
2060    #[test]
2061    fn helper_index_table_is_a_stable_bijection() {
2062        // Every helper round-trips through its external index, and the table
2063        // covers a dense 0..=31 range with unique symbols.
2064        let helpers = [
2065            Helper::LoadConstant,
2066            Helper::Unary,
2067            Helper::Binary,
2068            Helper::CreateObject,
2069            Helper::CreateArray,
2070            Helper::CreateClosure,
2071            Helper::GetProperty,
2072            Helper::SetProperty,
2073            Helper::DeleteProperty,
2074            Helper::Call,
2075            Helper::Construct,
2076            Helper::Import,
2077            Helper::Truthy,
2078            Helper::ResumeValue,
2079            Helper::DefineAccessor,
2080            Helper::LoadGlobal,
2081            Helper::StoreGlobal,
2082            Helper::TypeOfGlobal,
2083            Helper::LoadThis,
2084            Helper::LoadArguments,
2085            Helper::LoadNewTarget,
2086            Helper::ArrayPush,
2087            Helper::ArrayExtend,
2088            Helper::ObjectSpread,
2089            Helper::SetPrototype,
2090            Helper::CreatePrivateName,
2091            Helper::CreateRegExp,
2092            Helper::GetIterator,
2093            Helper::IteratorNext,
2094            Helper::Export,
2095            Helper::ConsumeFuel,
2096            Helper::CreateCell,
2097        ];
2098        let mut symbols = BTreeSet::new();
2099        for (expected_index, helper) in helpers.iter().copied().enumerate() {
2100            let index = helper.external_index();
2101            assert_eq!(index as usize, expected_index, "dense index for {helper:?}");
2102            assert_eq!(
2103                Helper::from_external_index(index),
2104                Some(helper),
2105                "round-trip for {helper:?}"
2106            );
2107            assert!(symbols.insert(helper.symbol()), "unique symbol {helper:?}");
2108        }
2109        assert_eq!(symbols.len(), 32);
2110        assert_eq!(Helper::from_external_index(32), None);
2111    }
2112
2113    #[test]
2114    fn load_const_routes_through_the_constant_helper() {
2115        let module = single(func(
2116            1,
2117            vec![load_undef(reg(0)), Instruction::Halt],
2118            Vec::new(),
2119        ));
2120        let (helpers, clif) = lower_one(&module);
2121        assert_eq!(helpers, vec![Helper::LoadConstant, Helper::ConsumeFuel]);
2122        assert_eq!(Helper::LoadConstant.symbol(), "bamts_load_constant");
2123        assert_eq!(Helper::LoadConstant.external_index(), 0);
2124        assert_eq!(Helper::from_external_index(0), Some(Helper::LoadConstant));
2125        assert!(
2126            clif.contains("u1:0"),
2127            "constant helper import missing:\n{clif}"
2128        );
2129        assert!(
2130            clif.contains("(i64, i32, i64) -> i32"),
2131            "constant helper sig wrong:\n{clif}"
2132        );
2133        assert!(clif.contains("call"), "helper call missing:\n{clif}");
2134    }
2135
2136    #[test]
2137    fn binary_routes_through_the_binary_helper() {
2138        let code = vec![
2139            load_undef(reg(0)),
2140            load_undef(reg(1)),
2141            Instruction::Binary {
2142                dst: reg(2),
2143                op: BinaryOp::Add,
2144                left: reg(0),
2145                right: reg(1),
2146            },
2147            Instruction::Halt,
2148        ];
2149        let module = single(func(3, code, Vec::new()));
2150        let (helpers, clif) = lower_one(&module);
2151        assert!(helpers.contains(&Helper::Binary));
2152        assert_eq!(Helper::Binary.external_index(), 2);
2153        assert!(
2154            clif.contains("u1:2"),
2155            "binary helper import missing:\n{clif}"
2156        );
2157        assert!(
2158            clif.contains("(i64, i32, i64, i64, i64) -> i32"),
2159            "binary helper sig wrong:\n{clif}"
2160        );
2161    }
2162
2163    #[test]
2164    fn unary_routes_through_the_unary_helper() {
2165        let code = vec![
2166            load_undef(reg(0)),
2167            Instruction::Unary {
2168                dst: reg(1),
2169                op: UnaryOp::Negate,
2170                operand: reg(0),
2171            },
2172            Instruction::Halt,
2173        ];
2174        let module = single(func(2, code, Vec::new()));
2175        let (helpers, clif) = lower_one(&module);
2176        assert!(helpers.contains(&Helper::Unary));
2177        assert!(
2178            clif.contains("u1:1"),
2179            "unary helper import missing:\n{clif}"
2180        );
2181        assert!(
2182            clif.contains("(i64, i32, i64, i64) -> i32"),
2183            "unary helper sig wrong:\n{clif}"
2184        );
2185    }
2186
2187    #[test]
2188    fn move_copies_registers_without_a_helper() {
2189        let code = vec![
2190            load_undef(reg(0)),
2191            Instruction::Move {
2192                dst: reg(1),
2193                src: reg(0),
2194            },
2195            Instruction::Halt,
2196        ];
2197        let module = single(func(2, code, Vec::new()));
2198        let (helpers, _) = lower_one(&module);
2199        // Move introduces only its explicit instruction-budget helper.
2200        assert_eq!(helpers, vec![Helper::LoadConstant, Helper::ConsumeFuel]);
2201    }
2202
2203    #[test]
2204    fn reachable_inline_pcs_each_emit_one_fuel_charge() {
2205        let code = vec![
2206            load_undef(reg(0)),
2207            Instruction::Move {
2208                dst: reg(1),
2209                src: reg(0),
2210            },
2211            Instruction::Jump { target: Pc::new(4) },
2212            Instruction::Binary {
2213                dst: reg(0),
2214                op: BinaryOp::Add,
2215                left: reg(0),
2216                right: reg(0),
2217            },
2218            Instruction::Halt,
2219        ];
2220        let module = single(func(2, code, Vec::new()));
2221        let (helpers, clif) = lower_one(&module);
2222        assert_eq!(helpers, vec![Helper::LoadConstant, Helper::ConsumeFuel]);
2223
2224        let declaration = clif
2225            .lines()
2226            .find(|line| line.contains("u1:30"))
2227            .expect("consume-fuel import");
2228        let function_ref = declaration
2229            .split_whitespace()
2230            .next()
2231            .expect("helper function reference");
2232        assert_eq!(
2233            clif.matches(&format!("call {function_ref}")).count(),
2234            3,
2235            "Move, Jump, and Halt each charge once; LoadConst and unreachable Binary do not:\n{clif}"
2236        );
2237    }
2238
2239    #[test]
2240    fn conditional_branch_coerces_via_truthy() {
2241        // r0 = undef; if truthy(r0) goto 3 else fall to 2; both halt.
2242        let code = vec![
2243            load_undef(reg(0)),
2244            Instruction::JumpIfTrue {
2245                condition: reg(0),
2246                target: Pc::new(3),
2247            },
2248            Instruction::Halt,
2249            Instruction::Halt,
2250        ];
2251        let module = single(func(1, code, Vec::new()));
2252        let (helpers, clif) = lower_one(&module);
2253        assert!(helpers.contains(&Helper::Truthy));
2254        assert_eq!(Helper::Truthy.external_index(), 12);
2255        assert!(
2256            clif.contains("u1:12"),
2257            "truthy helper import missing:\n{clif}"
2258        );
2259        // Truthy is total: two i64 params, one i32 result, no out-parameter.
2260        assert!(
2261            clif.contains("(i64, i64) -> i32"),
2262            "truthy helper sig wrong:\n{clif}"
2263        );
2264        assert!(clif.contains("brif"), "conditional branch missing:\n{clif}");
2265    }
2266
2267    #[test]
2268    fn jump_if_false_branches_with_inverted_polarity() {
2269        // pc0: r0 = undef. pc1: if !truthy(r0) goto pc3 else fall to pc2.
2270        // The truthy edge must fall through to pc2; the falsy edge must reach
2271        // the jump target pc3 — the argument-swapped mirror of JumpIfTrue.
2272        let code = vec![
2273            load_undef(reg(0)),
2274            Instruction::JumpIfFalse {
2275                condition: reg(0),
2276                target: Pc::new(3),
2277            },
2278            Instruction::Halt,
2279            Instruction::Halt,
2280        ];
2281        let module = single(func(1, code, Vec::new()));
2282        let (helpers, clif) = lower_one(&module);
2283        assert!(helpers.contains(&Helper::Truthy));
2284        let truthy_declaration = clif
2285            .lines()
2286            .find(|line| line.contains("u1:12"))
2287            .expect("truthy import");
2288        let truthy_ref = truthy_declaration
2289            .split_whitespace()
2290            .next()
2291            .expect("truthy function reference");
2292        let mut lines = clif.lines();
2293        lines
2294            .find(|line| line.contains(&format!("call {truthy_ref}")))
2295            .expect("truthy call");
2296        let brif = lines
2297            .find(|line| line.contains("brif"))
2298            .expect("conditional branch missing");
2299        let edges: Vec<u32> = brif
2300            .split(|c: char| !c.is_ascii_alphanumeric())
2301            .filter_map(|token| token.strip_prefix("block").and_then(|n| n.parse().ok()))
2302            .collect();
2303        assert_eq!(
2304            edges.len(),
2305            2,
2306            "conditional brif has two block edges:\n{clif}"
2307        );
2308        assert!(
2309            edges[0] < edges[1],
2310            "JumpIfFalse polarity: truthy edge must target the earlier fallthrough block:\n{clif}"
2311        );
2312    }
2313
2314    #[test]
2315    fn create_closure_passes_the_captures_value_not_an_index() {
2316        // r0 = undef (captures array placeholder); r1 = closure(fn 0, captures r0).
2317        let code = vec![
2318            load_undef(reg(0)),
2319            Instruction::CreateClosure {
2320                dst: reg(1),
2321                function: FunctionId::new(0),
2322                captures: reg(0),
2323            },
2324            Instruction::Halt,
2325        ];
2326        let module = single(func(2, code, Vec::new()));
2327        let (helpers, clif) = lower_one(&module);
2328        assert!(helpers.contains(&Helper::CreateClosure));
2329        assert_eq!(Helper::CreateClosure.external_index(), 5);
2330        assert!(
2331            clif.contains("u1:5"),
2332            "closure helper import missing:\n{clif}"
2333        );
2334        // (frame, function_id:i32, captures:i64, out) -> tag.
2335        assert!(
2336            clif.contains("(i64, i32, i64, i64) -> i32"),
2337            "closure helper sig wrong:\n{clif}"
2338        );
2339        // The captures register (r0) is loaded and passed as a value; the args
2340        // path performs no pointer arithmetic.
2341        assert!(
2342            !clif.contains("iadd"),
2343            "closure captures must be a value, not a computed pointer:\n{clif}"
2344        );
2345    }
2346
2347    #[test]
2348    fn call_passes_arguments_as_a_value_without_pointer_math() {
2349        // r0..r2 = undef; r3 = call r0 with this=r1 and arguments array r2.
2350        let code = vec![
2351            load_undef(reg(0)),
2352            load_undef(reg(1)),
2353            load_undef(reg(2)),
2354            Instruction::Call {
2355                dst: reg(3),
2356                callee: reg(0),
2357                this_value: reg(1),
2358                arguments: reg(2),
2359            },
2360            Instruction::Halt,
2361        ];
2362        let module = single(func(4, code, Vec::new()));
2363        let (helpers, clif) = lower_one(&module);
2364        assert!(helpers.contains(&Helper::Call));
2365        assert_eq!(Helper::Call.external_index(), 9);
2366        assert!(clif.contains("u1:9"), "call helper import missing:\n{clif}");
2367        // (frame, callee, this, arguments, out) -> tag; arguments is a value.
2368        assert!(
2369            clif.contains("(i64, i64, i64, i64, i64) -> i32"),
2370            "call helper sig wrong:\n{clif}"
2371        );
2372        // No fixed window: the arguments array is a register value, not a
2373        // computed base pointer.
2374        assert!(
2375            !clif.contains("iadd"),
2376            "arguments must be a value, not a window pointer:\n{clif}"
2377        );
2378    }
2379
2380    #[test]
2381    fn construct_passes_arguments_as_a_value() {
2382        let code = vec![
2383            load_undef(reg(0)),
2384            load_undef(reg(1)),
2385            Instruction::Construct {
2386                dst: reg(2),
2387                callee: reg(0),
2388                arguments: reg(1),
2389            },
2390            Instruction::Halt,
2391        ];
2392        let module = single(func(3, code, Vec::new()));
2393        let (helpers, clif) = lower_one(&module);
2394        assert!(helpers.contains(&Helper::Construct));
2395        assert_eq!(Helper::Construct.external_index(), 10);
2396        assert!(clif.contains("u1:10"), "construct import missing:\n{clif}");
2397        assert!(
2398            clif.contains("(i64, i64, i64, i64) -> i32"),
2399            "construct helper sig wrong:\n{clif}"
2400        );
2401    }
2402
2403    #[test]
2404    fn calls_scale_past_fixed_window_via_arguments_array() {
2405        // A single arguments-array register removes any fixed-window ceiling:
2406        // there is no arg_count operand and no per-argument addressing at all.
2407        let code = vec![
2408            load_undef(reg(0)),
2409            load_undef(reg(1)),
2410            load_undef(reg(2)),
2411            Instruction::Call {
2412                dst: reg(3),
2413                callee: reg(0),
2414                this_value: reg(1),
2415                arguments: reg(2),
2416            },
2417            Instruction::Halt,
2418        ];
2419        let module = single(func(4, code, Vec::new()));
2420        let (_, clif) = lower_one(&module);
2421        // The call carries exactly one arguments operand regardless of arity.
2422        assert!(
2423            !clif.contains("iadd"),
2424            "no window arithmetic for any arity:\n{clif}"
2425        );
2426    }
2427
2428    #[test]
2429    fn property_access_uses_a_register_key() {
2430        // r0 = object; r1 = key value; r2 = r0[r1]; r0[r1] = r1; delete r0[r1].
2431        let code = vec![
2432            Instruction::CreateObject { dst: reg(0) },
2433            load_undef(reg(1)),
2434            Instruction::GetProperty {
2435                dst: reg(2),
2436                object: reg(0),
2437                key: reg(1),
2438            },
2439            Instruction::SetProperty {
2440                object: reg(0),
2441                key: reg(1),
2442                value: reg(1),
2443            },
2444            Instruction::DeleteProperty {
2445                dst: reg(3),
2446                object: reg(0),
2447                key: reg(1),
2448            },
2449            Instruction::Halt,
2450        ];
2451        let module = single(func(4, code, Vec::new()));
2452        let (helpers, clif) = lower_one(&module);
2453        assert!(helpers.contains(&Helper::GetProperty));
2454        assert!(helpers.contains(&Helper::SetProperty));
2455        assert!(helpers.contains(&Helper::DeleteProperty));
2456        assert!(
2457            clif.contains("u1:6"),
2458            "get-property import missing:\n{clif}"
2459        );
2460        assert!(
2461            clif.contains("u1:7"),
2462            "set-property import missing:\n{clif}"
2463        );
2464        assert!(
2465            clif.contains("u1:8"),
2466            "delete-property import missing:\n{clif}"
2467        );
2468        // Register key: get/delete take (frame, object, key, out) all i64 ops.
2469        assert!(
2470            clif.contains("(i64, i64, i64, i64) -> i32"),
2471            "get/delete property sig wrong (register key):\n{clif}"
2472        );
2473        // Set takes (frame, object, key, value, out).
2474        assert!(
2475            clif.contains("(i64, i64, i64, i64, i64) -> i32"),
2476            "set property sig wrong (register key):\n{clif}"
2477        );
2478    }
2479
2480    #[test]
2481    fn define_accessor_carries_a_kind_selector() {
2482        let code = vec![
2483            Instruction::CreateObject { dst: reg(0) },
2484            load_undef(reg(1)),
2485            load_undef(reg(2)),
2486            Instruction::DefineAccessor {
2487                object: reg(0),
2488                key: reg(1),
2489                accessor: reg(2),
2490                kind: AccessorKind::Getter,
2491            },
2492            Instruction::Halt,
2493        ];
2494        let module = single(func(3, code, Vec::new()));
2495        let (helpers, clif) = lower_one(&module);
2496        assert!(helpers.contains(&Helper::DefineAccessor));
2497        assert_eq!(Helper::DefineAccessor.external_index(), 14);
2498        assert!(clif.contains("u1:14"), "accessor import missing:\n{clif}");
2499        // (frame, object, key, accessor, kind:i32, out) -> tag.
2500        assert!(
2501            clif.contains("(i64, i64, i64, i64, i32, i64) -> i32"),
2502            "accessor helper sig wrong:\n{clif}"
2503        );
2504    }
2505
2506    #[test]
2507    fn globals_lower_to_load_store_and_typeof_helpers() {
2508        let code = vec![
2509            Instruction::LoadGlobal {
2510                dst: reg(0),
2511                name: ConstantId::new(0),
2512            },
2513            Instruction::StoreGlobal {
2514                name: ConstantId::new(0),
2515                value: reg(0),
2516            },
2517            Instruction::TypeOfGlobal {
2518                dst: reg(1),
2519                name: ConstantId::new(0),
2520            },
2521            Instruction::Halt,
2522        ];
2523        let module = verified(
2524            vec![Constant::String(EcmaString::from_utf8("g"))],
2525            vec![func(2, code, Vec::new())],
2526        );
2527        let (helpers, clif) = lower_one(&module);
2528        assert!(helpers.contains(&Helper::LoadGlobal));
2529        assert!(helpers.contains(&Helper::StoreGlobal));
2530        assert!(helpers.contains(&Helper::TypeOfGlobal));
2531        assert!(
2532            clif.contains("u1:15"),
2533            "load-global import missing:\n{clif}"
2534        );
2535        assert!(
2536            clif.contains("u1:16"),
2537            "store-global import missing:\n{clif}"
2538        );
2539        assert!(
2540            clif.contains("u1:17"),
2541            "typeof-global import missing:\n{clif}"
2542        );
2543    }
2544
2545    #[test]
2546    fn this_arguments_new_target_are_total_and_unhandled() {
2547        // Total helpers (no throw) still write out.value; even under a covering
2548        // handler they must not mark the handler reachable, so LoadThis under a
2549        // handler still lowers cleanly with no handler routing helper.
2550        let code = vec![
2551            Instruction::LoadThis { dst: reg(0) },
2552            Instruction::LoadArguments { dst: reg(1) },
2553            Instruction::LoadNewTarget { dst: reg(2) },
2554            Instruction::Halt,
2555        ];
2556        let module = single(func(3, code, Vec::new()));
2557        let (helpers, clif) = lower_one(&module);
2558        assert!(helpers.contains(&Helper::LoadThis));
2559        assert!(helpers.contains(&Helper::LoadArguments));
2560        assert!(helpers.contains(&Helper::LoadNewTarget));
2561        assert!(clif.contains("u1:18"), "load-this import missing:\n{clif}");
2562        assert!(
2563            clif.contains("u1:19"),
2564            "load-arguments import missing:\n{clif}"
2565        );
2566        assert!(
2567            clif.contains("u1:20"),
2568            "load-new-target import missing:\n{clif}"
2569        );
2570    }
2571
2572    #[test]
2573    fn total_helper_under_handler_does_not_emit_handler_edge() {
2574        // A handler covers a total (non-throwing) TypeOfGlobal. Because it can
2575        // never throw, the handler pc is unreachable and not emitted; lowering
2576        // must still succeed (the abnormal edge propagates rather than jumping
2577        // to a non-existent block).
2578        let code = vec![
2579            Instruction::TypeOfGlobal {
2580                dst: reg(0),
2581                name: ConstantId::new(0),
2582            },
2583            Instruction::Halt,
2584            // pc 2: would-be handler, unreachable via the total op.
2585            load_undef(reg(0)),
2586            Instruction::Halt,
2587        ];
2588        let handlers = vec![ExceptionHandler {
2589            start: Pc::new(0),
2590            end: Pc::new(1),
2591            handler: Pc::new(2),
2592            catch_register: reg(0),
2593        }];
2594        let module = verified(
2595            vec![Constant::String(EcmaString::from_utf8("g"))],
2596            vec![func(1, code, handlers)],
2597        );
2598        // Must lower without panicking on a missing handler block.
2599        let lowered = lower_code_module(ModuleId::new(0), &module, host_config()).expect("lowers");
2600        assert!(lowered.functions[0].helpers.contains(&Helper::TypeOfGlobal));
2601    }
2602
2603    #[test]
2604    fn arrays_and_spreads_lower_to_their_helpers() {
2605        let code = vec![
2606            Instruction::CreateArray { dst: reg(0) },
2607            load_undef(reg(1)),
2608            Instruction::ArrayPush {
2609                array: reg(0),
2610                value: reg(1),
2611            },
2612            Instruction::ArrayExtend {
2613                array: reg(0),
2614                iterable: reg(1),
2615            },
2616            Instruction::CreateObject { dst: reg(2) },
2617            Instruction::ObjectSpread {
2618                target: reg(2),
2619                source: reg(1),
2620            },
2621            Instruction::Halt,
2622        ];
2623        let module = single(func(3, code, Vec::new()));
2624        let (helpers, clif) = lower_one(&module);
2625        assert!(helpers.contains(&Helper::ArrayPush));
2626        assert!(helpers.contains(&Helper::ArrayExtend));
2627        assert!(helpers.contains(&Helper::ObjectSpread));
2628        assert!(clif.contains("u1:21"), "array-push import missing:\n{clif}");
2629        assert!(
2630            clif.contains("u1:22"),
2631            "array-extend import missing:\n{clif}"
2632        );
2633        assert!(
2634            clif.contains("u1:23"),
2635            "object-spread import missing:\n{clif}"
2636        );
2637    }
2638
2639    #[test]
2640    fn prototype_private_and_regexp_lower_to_their_helpers() {
2641        let code = vec![
2642            Instruction::CreateObject { dst: reg(0) },
2643            Instruction::CreateObject { dst: reg(1) },
2644            Instruction::SetPrototype {
2645                object: reg(0),
2646                prototype: reg(1),
2647            },
2648            Instruction::CreatePrivateName {
2649                dst: reg(2),
2650                description: ConstantId::new(0),
2651            },
2652            Instruction::CreateRegExp {
2653                dst: reg(3),
2654                pattern: ConstantId::new(0),
2655                flags: ConstantId::new(1),
2656            },
2657            Instruction::Halt,
2658        ];
2659        let module = verified(
2660            vec![
2661                Constant::String(EcmaString::from_utf8("p")),
2662                Constant::String(EcmaString::from_utf8("g")),
2663            ],
2664            vec![func(4, code, Vec::new())],
2665        );
2666        let (helpers, clif) = lower_one(&module);
2667        assert!(helpers.contains(&Helper::SetPrototype));
2668        assert!(helpers.contains(&Helper::CreatePrivateName));
2669        assert!(helpers.contains(&Helper::CreateRegExp));
2670        assert!(
2671            clif.contains("u1:24"),
2672            "set-prototype import missing:\n{clif}"
2673        );
2674        assert!(
2675            clif.contains("u1:25"),
2676            "private-name import missing:\n{clif}"
2677        );
2678        assert!(clif.contains("u1:26"), "regexp import missing:\n{clif}");
2679        // RegExp carries two i32 constant selectors.
2680        assert!(
2681            clif.contains("(i64, i32, i32, i64) -> i32"),
2682            "regexp helper sig wrong:\n{clif}"
2683        );
2684    }
2685
2686    #[test]
2687    fn get_iterator_carries_a_kind_selector() {
2688        let code = vec![
2689            load_undef(reg(0)),
2690            Instruction::GetIterator {
2691                dst: reg(1),
2692                src: reg(0),
2693                kind: IteratorKind::Sync,
2694            },
2695            Instruction::Halt,
2696        ];
2697        let module = single(func(2, code, Vec::new()));
2698        let (helpers, clif) = lower_one(&module);
2699        assert!(helpers.contains(&Helper::GetIterator));
2700        assert_eq!(Helper::GetIterator.external_index(), 27);
2701        assert!(
2702            clif.contains("u1:27"),
2703            "get-iterator import missing:\n{clif}"
2704        );
2705        // (frame, src:i64, kind:i32, out) -> tag.
2706        assert!(
2707            clif.contains("(i64, i64, i32, i64) -> i32"),
2708            "get-iterator helper sig wrong:\n{clif}"
2709        );
2710    }
2711
2712    #[test]
2713    fn iterator_next_writes_both_done_and_value_registers() {
2714        // r0 = iterator; IteratorNext done=r1 value=r2 iterator=r0.
2715        let code = vec![
2716            load_undef(reg(0)),
2717            Instruction::IteratorNext {
2718                done: reg(1),
2719                value: reg(2),
2720                iterator: reg(0),
2721            },
2722            Instruction::Halt,
2723        ];
2724        let module = single(func(3, code, Vec::new()));
2725        let (helpers, clif) = lower_one(&module);
2726        assert!(helpers.contains(&Helper::IteratorNext));
2727        assert_eq!(Helper::IteratorNext.external_index(), 28);
2728        assert!(
2729            clif.contains("u1:28"),
2730            "iterator-next import missing:\n{clif}"
2731        );
2732        // (frame, iterator:i64, done_reg:i32, value_reg:i32, out) -> tag: the two
2733        // destination register indices are passed so the helper writes both.
2734        assert!(
2735            clif.contains("(i64, i64, i32, i32, i64) -> i32"),
2736            "iterator-next helper sig wrong:\n{clif}"
2737        );
2738        // Both destination register indices (r1 -> 1, r2 -> 2) are materialized
2739        // as i32 constants and handed to the helper.
2740        assert!(
2741            clif.contains("iconst.i32 1") && clif.contains("iconst.i32 2"),
2742            "both destination register indices must be passed:\n{clif}"
2743        );
2744    }
2745
2746    #[test]
2747    fn iterator_next_under_handler_binds_catch_on_throw() {
2748        // A throwing IteratorNext under a handler routes its Throw to the catch.
2749        let code = vec![
2750            load_undef(reg(0)),
2751            Instruction::IteratorNext {
2752                done: reg(1),
2753                value: reg(2),
2754                iterator: reg(0),
2755            },
2756            Instruction::Halt,
2757            // pc 3: handler.
2758            Instruction::Halt,
2759        ];
2760        let handlers = vec![ExceptionHandler {
2761            start: Pc::new(0),
2762            end: Pc::new(3),
2763            handler: Pc::new(3),
2764            catch_register: reg(0),
2765        }];
2766        let module = single(func(3, code, handlers));
2767        let clif = clif_of(&module);
2768        // Normal-vs-abnormal then throw-vs-propagate around the throwing op.
2769        let brif_count = clif.matches("brif").count();
2770        assert!(
2771            brif_count >= 2,
2772            "expected handler routing brifs, got {brif_count}:\n{clif}"
2773        );
2774    }
2775
2776    #[test]
2777    fn export_lowers_to_the_export_helper() {
2778        let code = vec![
2779            load_undef(reg(0)),
2780            Instruction::Export {
2781                name: ConstantId::new(0),
2782                src: reg(0),
2783            },
2784            Instruction::Halt,
2785        ];
2786        let module = verified(
2787            vec![Constant::String(EcmaString::from_utf8("x"))],
2788            vec![func(1, code, Vec::new())],
2789        );
2790        let (helpers, clif) = lower_one(&module);
2791        assert!(helpers.contains(&Helper::Export));
2792        assert_eq!(Helper::Export.external_index(), 29);
2793        assert!(clif.contains("u1:29"), "export import missing:\n{clif}");
2794        assert!(
2795            clif.contains("(i64, i32, i64, i64) -> i32"),
2796            "export helper sig wrong:\n{clif}"
2797        );
2798    }
2799
2800    #[test]
2801    fn suspend_uses_a_resume_token_and_resume_helper() {
2802        // r0 = undef; suspend (yield r0), resume at pc 2; pc 2 halts.
2803        let code = vec![
2804            load_undef(reg(0)),
2805            Instruction::Suspend {
2806                dst: reg(0),
2807                src: reg(0),
2808                resume: Pc::new(2),
2809            },
2810            Instruction::Halt,
2811        ];
2812        let module = single(func(1, code, Vec::new()));
2813        let lowered = lower_code_module(ModuleId::new(0), &module, host_config()).expect("lowers");
2814        let function = &lowered.functions[0];
2815        // Fresh token 0 plus the suspend at pc 1 -> token 2.
2816        assert_eq!(function.entry_points, vec![0, 2]);
2817        assert!(function.helpers.contains(&Helper::ResumeValue));
2818        assert_eq!(Helper::ResumeValue.external_index(), 13);
2819        let clif = function.clif.display().to_string();
2820        // Multi-token dispatch loads and compares the resume token.
2821        assert!(
2822            clif.contains("load.i32"),
2823            "dispatch token load missing:\n{clif}"
2824        );
2825        assert!(clif.contains("icmp"), "dispatch compare missing:\n{clif}");
2826        // The suspend stores its resume token into the frame.
2827        assert!(
2828            clif.contains("store"),
2829            "resume token store missing:\n{clif}"
2830        );
2831        assert!(
2832            clif.contains("u1:13"),
2833            "resume helper import missing:\n{clif}"
2834        );
2835    }
2836
2837    #[test]
2838    fn throwing_op_under_a_handler_routes_and_binds_catch_register() {
2839        // handler covers [0,3), dispatches to pc 3 binding into r0.
2840        let code = vec![
2841            load_undef(reg(0)),
2842            load_undef(reg(1)),
2843            Instruction::Binary {
2844                dst: reg(2),
2845                op: BinaryOp::Add,
2846                left: reg(0),
2847                right: reg(1),
2848            },
2849            Instruction::Halt,
2850        ];
2851        let handlers = vec![ExceptionHandler {
2852            start: Pc::new(0),
2853            end: Pc::new(3),
2854            handler: Pc::new(3),
2855            catch_register: reg(0),
2856        }];
2857        let module = single(func(3, code, handlers));
2858        let clif = clif_of(&module);
2859        // Around the throwing Binary: normal-vs-abnormal, then throw-vs-propagate.
2860        let brif_count = clif.matches("brif").count();
2861        assert!(
2862            brif_count >= 2,
2863            "expected handler routing brifs, got {brif_count}:\n{clif}"
2864        );
2865        assert!(
2866            clif.contains("icmp"),
2867            "throw discriminator missing:\n{clif}"
2868        );
2869    }
2870
2871    #[test]
2872    fn explicit_throw_binds_catch_register_and_jumps() {
2873        // r0 = undef; throw r0; handler at pc 2 binds into r0.
2874        let code = vec![
2875            load_undef(reg(0)),
2876            Instruction::Throw { value: reg(0) },
2877            Instruction::Halt,
2878        ];
2879        let handlers = vec![ExceptionHandler {
2880            start: Pc::new(0),
2881            end: Pc::new(2),
2882            handler: Pc::new(2),
2883            catch_register: reg(0),
2884        }];
2885        let module = single(func(1, code, handlers));
2886        let (helpers, clif) = lower_one(&module);
2887        // A locally-caught throw needs only its fuel helper and jumps to the handler.
2888        assert_eq!(helpers, vec![Helper::LoadConstant, Helper::ConsumeFuel]);
2889        assert!(clif.contains("jump"), "handler jump missing:\n{clif}");
2890        assert!(
2891            clif.contains("store"),
2892            "catch-register bind missing:\n{clif}"
2893        );
2894    }
2895
2896    #[test]
2897    fn return_writes_completion_and_normal_tag() {
2898        let code = vec![load_undef(reg(0)), Instruction::Return { value: reg(0) }];
2899        let module = single(func(1, code, Vec::new()));
2900        let (helpers, clif) = lower_one(&module);
2901        assert_eq!(helpers, vec![Helper::LoadConstant, Helper::ConsumeFuel]);
2902        assert!(
2903            clif.contains("store"),
2904            "return value store missing:\n{clif}"
2905        );
2906        assert!(clif.contains("return"), "return missing:\n{clif}");
2907    }
2908
2909    #[test]
2910    fn high_register_offsets_scale_past_127() {
2911        // A register id past 127 (r500) addresses at byte offset 500*8 = 4000,
2912        // a full 32-bit displacement, not a signed byte. LoadConst into r500
2913        // stores out.value at that offset.
2914        let code = vec![load_undef(reg(500)), Instruction::Halt];
2915        let module = single(func(501, code, Vec::new()));
2916        let (_, clif) = lower_one(&module);
2917        assert!(
2918            clif.contains("+4000"),
2919            "expected a +4000 byte offset for r500:\n{clif}"
2920        );
2921    }
2922
2923    #[test]
2924    fn import_and_closure_are_lowered() {
2925        let code = vec![
2926            load_undef(reg(0)),
2927            Instruction::CreateClosure {
2928                dst: reg(1),
2929                function: FunctionId::new(0),
2930                captures: reg(0),
2931            },
2932            Instruction::Import {
2933                dst: reg(2),
2934                specifier: ConstantId::new(0),
2935            },
2936            Instruction::Halt,
2937        ];
2938        let module = verified(
2939            vec![Constant::String(EcmaString::from_utf8("mod"))],
2940            vec![func(3, code, Vec::new())],
2941        );
2942        let (helpers, _) = lower_one(&module);
2943        assert!(helpers.contains(&Helper::CreateClosure));
2944        assert!(helpers.contains(&Helper::Import));
2945    }
2946
2947    #[test]
2948    fn lowering_is_deterministic() {
2949        let code = vec![
2950            load_undef(reg(0)),
2951            Instruction::Binary {
2952                dst: reg(1),
2953                op: BinaryOp::Add,
2954                left: reg(0),
2955                right: reg(0),
2956            },
2957            Instruction::Jump { target: Pc::new(3) },
2958            Instruction::Halt,
2959        ];
2960        let make = || single(func(2, code.clone(), Vec::new()));
2961        let a = clif_of(&make());
2962        let b = clif_of(&make());
2963        assert_eq!(a, b);
2964    }
2965
2966    #[test]
2967    fn unreachable_code_is_not_emitted() {
2968        // Jump over the Binary straight to Halt; the Binary is unreachable and
2969        // must not lower a helper.
2970        let code = vec![
2971            Instruction::Jump { target: Pc::new(2) },
2972            Instruction::Binary {
2973                dst: reg(0),
2974                op: BinaryOp::Add,
2975                left: reg(0),
2976                right: reg(0),
2977            },
2978            Instruction::Halt,
2979        ];
2980        let module = single(func(1, code, Vec::new()));
2981        let (helpers, _) = lower_one(&module);
2982        assert_eq!(
2983            helpers,
2984            vec![Helper::ConsumeFuel],
2985            "unreachable Binary must not lower its helper"
2986        );
2987    }
2988
2989    #[test]
2990    fn multiple_functions_get_distinct_symbols() {
2991        let functions = vec![
2992            func(0, vec![Instruction::Halt], Vec::new()),
2993            func(0, vec![Instruction::Halt], Vec::new()),
2994        ];
2995        let module = verified(Vec::new(), functions);
2996        let lowered = lower_code_module(ModuleId::new(0), &module, host_config()).expect("lowers");
2997        assert_eq!(lowered.functions.len(), 2);
2998        assert_eq!(lowered.functions[0].symbol, "bamts_m0_fn_0");
2999        assert_eq!(lowered.functions[1].symbol, "bamts_m0_fn_1");
3000        let name0 = lowered.functions[0].clif.display().to_string();
3001        assert!(name0.contains("u0:0"), "function 0 name wrong:\n{name0}");
3002        let name1 = lowered.functions[1].clif.display().to_string();
3003        assert!(name1.contains("u0:1"), "function 1 name wrong:\n{name1}");
3004    }
3005
3006    #[test]
3007    fn innermost_handler_prefers_the_tightest_interval() {
3008        let outer = ExceptionHandler {
3009            start: Pc::new(0),
3010            end: Pc::new(10),
3011            handler: Pc::new(20),
3012            catch_register: reg(0),
3013        };
3014        let inner = ExceptionHandler {
3015            start: Pc::new(2),
3016            end: Pc::new(6),
3017            handler: Pc::new(30),
3018            catch_register: reg(1),
3019        };
3020        let handlers = [outer, inner];
3021        assert_eq!(
3022            innermost_handler(&handlers, 4).map(|h| h.handler),
3023            Some(Pc::new(30))
3024        );
3025        assert_eq!(
3026            innermost_handler(&handlers, 8).map(|h| h.handler),
3027            Some(Pc::new(20))
3028        );
3029        assert_eq!(innermost_handler(&handlers, 10), None);
3030    }
3031
3032    #[test]
3033    fn non_64_bit_targets_are_rejected() {
3034        let config = TargetFrontendConfig {
3035            default_call_conv: CallConv::SystemV,
3036            pointer_width: {
3037                let flags = Flags::new(settings::builder());
3038                match isa::lookup_by_name("i686")
3039                    .ok()
3040                    .and_then(|b| b.finish(flags).ok())
3041                {
3042                    Some(target) => target.frontend_config().pointer_width,
3043                    None => return, // no 32-bit ISA compiled in; nothing to test
3044                }
3045            },
3046            page_size_align_log2: 12,
3047        };
3048        let module = single(func(0, vec![Instruction::Halt], Vec::new()));
3049        let error =
3050            lower_code_module(ModuleId::new(0), &module, config).expect_err("32-bit rejected");
3051        assert!(matches!(
3052            error,
3053            LowerError::UnsupportedPointerWidth { bits: 32 }
3054        ));
3055    }
3056
3057    #[test]
3058    fn error_display_is_stable() {
3059        let width = LowerError::UnsupportedPointerWidth { bits: 32 };
3060        assert!(width.to_string().contains("64-bit"));
3061        let many = LowerError::TooManyFunctions { count: 200 };
3062        assert!(many.to_string().contains("200"));
3063        let slots = LowerError::RegisterFileTooLarge {
3064            function: FunctionId::new(1),
3065            register_count: 9,
3066        };
3067        assert!(slots.to_string().contains("function 1"));
3068        let sig = LowerError::EntrySignatureMismatch {
3069            function: FunctionId::new(2),
3070        };
3071        assert!(sig.to_string().contains("function 2"));
3072        let ir = LowerError::IrVerification {
3073            function: FunctionId::new(3),
3074            message: "boom".to_string(),
3075        };
3076        let text = ir.to_string();
3077        assert!(text.contains("function 3"));
3078        assert!(text.contains("boom"));
3079    }
3080
3081    #[test]
3082    fn constant_pool_does_not_perturb_lowering() {
3083        let functions = vec![BytecodeFunction::new(
3084            Some(ConstantId::new(0)),
3085            0,
3086            0,
3087            0,
3088            FunctionFlags::default(),
3089            vec![Instruction::Halt],
3090            Vec::new(),
3091        )];
3092        let module = Module::new(
3093            vec![Constant::String(EcmaString::from_utf8("main"))],
3094            functions,
3095            FunctionId::new(0),
3096        )
3097        .verify()
3098        .expect("verifies");
3099        let lowered = lower_code_module(ModuleId::new(0), &module, host_config()).expect("lowers");
3100        assert_eq!(lowered.functions[0].symbol, "bamts_m0_fn_0");
3101    }
3102    #[test]
3103    fn program_lowering_retains_module_local_ids_and_entry_tuple() {
3104        let make_module = |name: &str| bamts_bytecode::ProgramModule {
3105            name: ConstantId::new(0),
3106            code: Module::new(
3107                vec![Constant::String(EcmaString::from_utf8(name))],
3108                vec![func(0, vec![Instruction::Halt], Vec::new())],
3109                FunctionId::new(0),
3110            )
3111            .verify()
3112            .expect("module verifies"),
3113            edges: Vec::new(),
3114            bindings: Vec::new(),
3115            exports: Vec::new(),
3116        };
3117        let program = Program::link(
3118            vec![make_module("dependency"), make_module("entry")],
3119            ModuleId::new(1),
3120        )
3121        .expect("program verifies");
3122
3123        let lowered = lower_program(&program, host_config()).expect("program lowers");
3124        assert_eq!(lowered.modules.len(), 2);
3125        assert_eq!(lowered.modules[0].id, ModuleId::new(0));
3126        assert_eq!(lowered.modules[1].id, ModuleId::new(1));
3127        assert_eq!(lowered.modules[0].functions[0].id, FunctionId::new(0));
3128        assert_eq!(lowered.modules[1].functions[0].id, FunctionId::new(0));
3129        assert_eq!(lowered.modules[0].functions[0].symbol, "bamts_m0_fn_0");
3130        assert_eq!(lowered.modules[1].functions[0].symbol, "bamts_m1_fn_0");
3131        assert_eq!(lowered.entry_module, ModuleId::new(1));
3132        assert_eq!(lowered.entry_function, FunctionId::new(0));
3133    }
3134}