Expand description
Shared, backend-neutral Cranelift lowering for verified BamTS bytecode.
This crate turns a canonical bamts_bytecode::Program<Verified> into
Cranelift IR through lower_program. It retains one LoweredModule per
program module, with module-local pools and module-qualified native symbols,
for both feature-gated backends:
- a
host-jitbackend that finalizes eachir::Functioninto executable memory, and - an
aotbackend that emits eachir::Functioninto an object file.
This slice performs no executable-memory allocation and no object
linking; it only builds and verifies IR. Both later backends supply their
own [isa::TargetFrontendConfig] (via isa.frontend_config()), so the ISA
choice, calling convention, and pointer type stay outside this crate.
§Entry ABI
Every lowered function has the native-entry signature from the canonical
execution plan (N5), matching bamts_native::ShadowFrame and
bamts_native::Completion:
extern "C" fn(frame: *mut ShadowFrame, out: *mut Completion) -> u32framepoints at the register frame;frame.handles(offset 16) is the*mut Valueregister array.frame.bytecode_pc(offset 8) records the active instruction and carries the resume token afterSuspend.outreceives the completion value; the returnedu32is abamts_native::CompletionTagdiscriminant (Normal/Throw/Suspend/FatalTrap).
§Register addressing convention
Register r[i] lives at frame.handles + i * 8 (one Value/u64 slot).
Every access derives the byte offset as i64::from(register.get()) * 8; the
validation pass ([validate_slots]) proves this offset fits the Offset32
used by loads and stores, so u32 register ids and CLIF addresses never mix
widths inconsistently. This holds for register ids well past 127: a slot
offset is a full 32-bit displacement, not a signed byte.
§Dynamic operands: no fixed windows, no constant-keyed properties
The production ISA carries no fixed argument window and no constant-keyed
property access. Calls and constructs take a single arguments array in a
register (Call/Construct arguments), so spread and any arity flow
through one Value handle with no pointer arithmetic. Property access takes
its key from a register (GetProperty/SetProperty/DeleteProperty
key), a Value the runtime coerces to a property key (string, symbol, or
private name). Closures capture through an array register
(CreateClosure captures), again a single Value handle.
§Value semantics: the explicit helper ABI
The bytecode algebra (bamts_bytecode::Instruction) is structural: the
verifier proves definite initialization and CFG validity but assigns no
value meaning. The NaN-boxed runtime Value requires tag dispatch that this
IR-only slice must not open-code, so every operation whose result depends on
runtime value semantics is lowered to a call into a declared Helper
(u1:<index> external names a backend resolves to a C symbol). This crate
declares each helper’s ABI and control-flow contract; it never defines the
helper body.
Every value-producing helper follows one completion ABI:
fn(frame, <operands…>, out: *mut Completion) -> u32(tag). On
Normal (0) the result is in out.value; on Throw the thrown handle is in
out.value and control routes to a covering handler; FatalTrap always
propagates to the runtime. Two exceptions to the “result in out.value”
rule:
Helper::Truthyperforms the total ToBoolean coercion and returns the truth value directly as0/1; it never writesoutand never throws.Helper::IteratorNextwrites two registers — it receives thedoneandvalueregister indices and, onNormal, writes both slots in the frame directly (a single completion channel cannot carry two results);out.valueis used only to carry a thrown handle onThrow.
A subset of the completion helpers is total (Normal only, never
Throw/FatalTrap): Helper::TypeOfGlobal, Helper::LoadThis,
Helper::LoadArguments, Helper::LoadNewTarget, and
Helper::CreatePrivateName. They still use the completion ABI (result in
out.value) but their abnormal edge is unreachable, so they never mark a
handler block reachable.
§Opcode ledger (every variant has an explicit path)
| Opcode | Lowering |
|---|---|
LoadConst | Helper::LoadConstant by ConstantId → dst |
Move | inline copy handles[src] → handles[dst] |
Unary | Helper::Unary with the operator selector |
Binary | Helper::Binary with the operator selector |
CreateObject | Helper::CreateObject → dst |
CreateArray | Helper::CreateArray → dst |
CreateCell | Helper::CreateCell → dst |
CreateClosure | Helper::CreateClosure (function, captures array)→dst |
GetProperty | Helper::GetProperty (object, register key) → dst |
SetProperty | Helper::SetProperty (object, register key, value) |
DeleteProperty | Helper::DeleteProperty (object, register key) → dst |
DefineAccessor | Helper::DefineAccessor (object, key, accessor, kind) |
Call | Helper::Call (callee, this, arguments array) → dst |
Construct | Helper::Construct (callee, arguments array) → dst |
LoadGlobal | Helper::LoadGlobal by string name → dst |
StoreGlobal | Helper::StoreGlobal (string name, value) |
TypeOfGlobal | Helper::TypeOfGlobal by string name → dst (total) |
LoadThis | Helper::LoadThis → dst (total) |
LoadArguments | Helper::LoadArguments → dst (total) |
LoadNewTarget | Helper::LoadNewTarget → dst (total) |
ArrayPush | Helper::ArrayPush (array, value) |
ArrayExtend | Helper::ArrayExtend (array, iterable) |
ObjectSpread | Helper::ObjectSpread (target, source) |
SetPrototype | Helper::SetPrototype (object, prototype) |
CreatePrivateName | Helper::CreatePrivateName by description → dst (total) |
CreateRegExp | Helper::CreateRegExp (pattern, flags) → dst |
GetIterator | Helper::GetIterator (src, kind) → dst |
IteratorNext | Helper::IteratorNext (iterator) → done + value |
Import | Helper::Import by string specifier → dst |
Export | Helper::Export (string name, src) |
Jump | unconditional branch |
JumpIfTrue | Helper::Truthy then conditional branch |
JumpIfFalse | Helper::Truthy then conditional branch |
Return | handles[value] → out.value, return Normal |
Throw | route to covering handler (bind catch_register) or |
out.value + return Throw | |
Suspend | yield path + resume path via Helper::ResumeValue |
Halt | undefined → out.value, return Normal |
No opcode is silently dropped and none is lowered to a placeholder no-op.
§Exceptions
When a completion-helper call returns Throw and a bytecode handler covers
the current pc, control branches to that handler’s block after storing the
thrown value (out.value) into the handler’s catch_register slot; the
explicit Throw opcode binds its operand into catch_register directly.
FatalTrap bypasses handlers. When no handler covers the pc, the completion
is returned to the caller.
§Suspend and the resume helper
Suspend { dst, src, resume } yields src and, when resumed, delivers the
resumed value into dst before continuing at resume. The native entry ABI
carries no resume input (out.value is the yielded value, not an input),
so the resumed value is obtained through an explicit runtime contract rather
than invented:
- Yield path — store this suspend’s resume token into
frame.bytecode_pc(0is a fresh call; the suspend at bytecode pcPuses tokenP + 1, so tokens never collide with a fresh entry or with each other), writesrcintoout.value, and returnSuspend. - Resume path — the dispatch prologue for token
P + 1callsHelper::ResumeValue, which the runtime resolves to write the verified resumed value for this frame intoout.value(it may returnThrowforgenerator.throw, routed to a covering handler, orFatalTrap); the resumed value is then stored intodstand control continues atresume.
bamts_bytecode currently exposes no ABI for the resume input, so
Helper::ResumeValue is a new required contract the runtime must
provide for any module that suspends.
Structs§
- Lowered
Function - One lowered function: its Cranelift IR plus the metadata a backend needs to compile and link it without re-deriving anything.
- Lowered
Module - The complete lowering of one verified module within a program.
- Lowered
Program - The shared lowering of one canonical verified program.
- Program
Lower Error - A deterministic lowering failure anchored to its canonical program module.
Enums§
- Helper
- A runtime routine the lowered code calls but does not define. Backends
resolve each
Helper::symbolto an address (JIT) or relocation (AOT). - Lower
Error - A deterministic, typed lowering failure.
Constants§
- FUNCTION_
NAMESPACE - Cranelift external-name namespace for lowered bytecode functions: a name
u0:<index>refers to the lowered function whoseFunctionIdisindex. - HELPER_
NAMESPACE - Cranelift external-name namespace for runtime helper imports: a name
u1:<index>refers to theHelperwithHelper::external_indexequal toindex.
Functions§
- function_
symbol - The collision-free linker symbol for a module-qualified lowered function.
- lower_
program - Lowers every function of every module in a verified canonical program.