Skip to main content

Crate bamts_codegen

Crate bamts_codegen 

Source
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-jit backend that finalizes each ir::Function into executable memory, and
  • an aot backend that emits each ir::Function into 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) -> u32
  • frame points at the register frame; frame.handles (offset 16) is the *mut Value register array. frame.bytecode_pc (offset 8) records the active instruction and carries the resume token after Suspend.
  • out receives the completion value; the returned u32 is a bamts_native::CompletionTag discriminant (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::Truthy performs the total ToBoolean coercion and returns the truth value directly as 0/1; it never writes out and never throws.
  • Helper::IteratorNext writes two registers — it receives the done and value register indices and, on Normal, writes both slots in the frame directly (a single completion channel cannot carry two results); out.value is used only to carry a thrown handle on Throw.

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)

OpcodeLowering
LoadConstHelper::LoadConstant by ConstantIddst
Moveinline copy handles[src]handles[dst]
UnaryHelper::Unary with the operator selector
BinaryHelper::Binary with the operator selector
CreateObjectHelper::CreateObjectdst
CreateArrayHelper::CreateArraydst
CreateCellHelper::CreateCelldst
CreateClosureHelper::CreateClosure (function, captures array)→dst
GetPropertyHelper::GetProperty (object, register key) → dst
SetPropertyHelper::SetProperty (object, register key, value)
DeletePropertyHelper::DeleteProperty (object, register key) → dst
DefineAccessorHelper::DefineAccessor (object, key, accessor, kind)
CallHelper::Call (callee, this, arguments array) → dst
ConstructHelper::Construct (callee, arguments array) → dst
LoadGlobalHelper::LoadGlobal by string namedst
StoreGlobalHelper::StoreGlobal (string name, value)
TypeOfGlobalHelper::TypeOfGlobal by string namedst (total)
LoadThisHelper::LoadThisdst (total)
LoadArgumentsHelper::LoadArgumentsdst (total)
LoadNewTargetHelper::LoadNewTargetdst (total)
ArrayPushHelper::ArrayPush (array, value)
ArrayExtendHelper::ArrayExtend (array, iterable)
ObjectSpreadHelper::ObjectSpread (target, source)
SetPrototypeHelper::SetPrototype (object, prototype)
CreatePrivateNameHelper::CreatePrivateName by description → dst (total)
CreateRegExpHelper::CreateRegExp (pattern, flags) → dst
GetIteratorHelper::GetIterator (src, kind) → dst
IteratorNextHelper::IteratorNext (iterator) → done + value
ImportHelper::Import by string specifierdst
ExportHelper::Export (string name, src)
Jumpunconditional branch
JumpIfTrueHelper::Truthy then conditional branch
JumpIfFalseHelper::Truthy then conditional branch
Returnhandles[value]out.value, return Normal
Throwroute to covering handler (bind catch_register) or
out.value + return Throw
Suspendyield path + resume path via Helper::ResumeValue
Haltundefinedout.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 (0 is a fresh call; the suspend at bytecode pc P uses token P + 1, so tokens never collide with a fresh entry or with each other), write src into out.value, and return Suspend.
  • Resume path — the dispatch prologue for token P + 1 calls Helper::ResumeValue, which the runtime resolves to write the verified resumed value for this frame into out.value (it may return Throw for generator.throw, routed to a covering handler, or FatalTrap); the resumed value is then stored into dst and control continues at resume.

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§

LoweredFunction
One lowered function: its Cranelift IR plus the metadata a backend needs to compile and link it without re-deriving anything.
LoweredModule
The complete lowering of one verified module within a program.
LoweredProgram
The shared lowering of one canonical verified program.
ProgramLowerError
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::symbol to an address (JIT) or relocation (AOT).
LowerError
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 whose FunctionId is index.
HELPER_NAMESPACE
Cranelift external-name namespace for runtime helper imports: a name u1:<index> refers to the Helper with Helper::external_index equal to index.

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.