ABI invocation and execution
Documentation status: reference — see Maturity and evidence.
Invocation identity
Each callable ABI member has a stable invocation identity:
(moduleId, typeId, methodId, callKind)
callKind distinguishes constructor, static, and instance calls. The descriptor also records whether a receiver is required, so call identity and physical receiver validation remain explicit.
Execution pipeline
The current engine executes a native call through this pipeline:
flowchart LR
DESC[Method descriptor] --> VALIDATE[Validate receiver and arity]
ARGS[Typed ABI values] --> PACK[Build argument record]
PACK --> CALL[Invoke native module]
DESC --> CALL
CALL --> CODE[Check result code]
CODE --> DECODE[Decode result storage]
DECODE --> OWN[Attach ownership metadata]
The caller does not hand-build a C-style argument structure. Packing is driven by the descriptor loaded from the manifest.
Receiver rules
Before native execution:
- a member marked as requiring a receiver fails if the handle is absent;
- a member marked as not accepting a receiver fails if a handle is supplied.
This prevents accidental constructor/static versus instance mismatches from crossing the native boundary.
Arity and layout
The argument builder validates:
- runtime argument count equals descriptor parameter count;
- the layout's declared parameter count equals the descriptor array length;
- the argument record size is non-negative;
- every parameter offset and size stays inside the record;
- the parameter direction is currently supported;
- the runtime value kind matches the descriptor kind;
- pointer-sized values match the host pointer size.
The buffer is zero-initialized before values are written. This gives deterministic padding bytes for the current packed-record contract.
Native result handling
The executor allocates result storage according to the descriptor. The current certified generic path handles:
void;i32;- pointer;
- handle.
After the module returns, a non-success result code becomes an execution failure that includes the module's last-error text when available.
Release after execution
Decoding does not erase ownership information. A returned handle carries enough metadata for the executor to determine whether it must later call the module release function.
This is important for generated bindings: the wrapper may look like an ordinary managed object, but its deterministic disposal ultimately follows this ABI ownership contract.
Tested end-to-end path
The current engine tests include source-level FPL expressions that are compiled into ABI invocation expressions and then executed through:
source expression
-> ABI linker
-> manifest descriptor
-> argument buffer
-> native module
-> decoded FPL value
The tested scenarios include constructor execution, instance invocation, pointer-returning access, and explicit release of an owned native handle.