Skip to content
EN FR

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.