Skip to content

invoke

View as Markdown

The invoke syscall calls another program, creating a new execution frame. This enables cross-program invocation and modularity in the ThruVM runtime.

Code: 0x0A (TN_SYSCALL_CODE_INVOKE)

ulong tsys_invoke(void const* instr_data,
ulong instr_data_sz,
ushort program_account_idx,
tsdk_invoke_auth_t const* auth,
ulong* invoke_err_code);

instr_data · void const · required*

Pointer to the instruction data to pass to the invoked program.

instr_data_sz · ulong · required

Length of the instruction data in bytes.

program_account_idx · ushort · required

Index of the program account to invoke. Must be a valid program account.

auth · tsdk_invoke_auth_t const · required*

Optional invoke authorization descriptor. Pass NULL to use default authorization behavior. When provided, the descriptor is validated by the SDK before the syscall is issued.

invoke_err_code · ulong · required*

Pointer to store the error code returned by the invoked program. This will be set to the exit code of the invoked program.

Returns a syscall result code:

Success · ulong

  • TN_VM_SYSCALL_SUCCESS (0) - Program invocation initiated successfully

Error Codes · ulong

  • TN_VM_ERR_SYSCALL_INVALID_ACCOUNT_INDEX (-8) - Program account index out of bounds
  • TN_VM_ERR_SYSCALL_ACCOUNT_DOES_NOT_EXIST (-9) - Program account does not exist
  • TN_VM_ERR_SYSCALL_ACCOUNT_IS_NOT_PROGRAM (-17) - Account is not a valid program
  • TN_VM_ERR_SYSCALL_CALL_DEPTH_TOO_DEEP (-24) - Maximum call depth exceeded
  • Base cost: TN_VM_SYSCALL_BASE_COST (512 units)
    • Represents the overhead of saving and restoring VM register state
    • Implementation details:
      • Save operation: All 32 registers (256 bytes) are saved to the public shadow stack
      • Restore operation: When returning from the invoked program (via exit syscall), the registers (256 bytes) are restored from the public shadow stack back to the VM
      • Total memory operations per invoke/exit cycle: 512 bytes (256 saved + 256 restored)
    • Cost calculation: 32 registers × 8 bytes × 2 operations (save + restore) = 512 CUs
  • Additional cost: None for the invoke syscall itself
  • Note: The invoked program will consume additional compute units during its execution
  • Page usage: No direct page allocation
  • Shadow stack: Uses existing shadow stack pages to save execution state
  • Call overhead: Minimal memory overhead for call frame management
  • Execution context: Creates new execution frame and updates call stack
  • Register setup: Sets a0 to instruction data address, a1 to data length
  • Program counter: Resets PC to 0 for the new program
  • Call tracking: Increments frame and call indices
  • Shadow stack: Saves current execution state on shadow stack
    • Stores all 32 registers (256 bytes) in the public shadow stack
    • Public shadow stack is read-only memory accessible to programs via memory segment 0x0002
    • Preserves program context including stack/heap page counts
    • Creates accessible snapshot for cross-program inspection and state restoration

The syscall manages a call stack with the following updates:

  • Frame index: Incremented to create new frame
  • Call depth: Updated to reflect nesting level
  • Shadow stack: Current state saved for restoration on return
  • Program context: Switches to the invoked program’s bytecode
  • The target account must have the PROGRAM flag set
  • Program bytecode is validated before invocation
  • Account must exist and be accessible in the transaction
  • Maximum call depth is enforced (TN_VM_CALL_DEPTH_MAX)
  • Prevents infinite recursion and stack overflow
  • Call depth is tracked per execution frame

Upon successful invocation:

  • a0: Set to instruction data virtual address
  • a1: Set to instruction data length
  • PC: Reset to 0 (start of invoked program)
  • Other registers: Preserved from calling context
  • Instruction data can be any format understood by the target program
  • The invoked program executes with the same transaction context
  • Account permissions and writability are preserved
  • Return values are passed through program exit mechanisms
  • If auth is non-NULL, SDK-side validation runs before the syscall:
    • bad auth magic: 0xBAD0A170
    • auth account not owned by current program: 0xBAD0A171
    • invalid account index in auth/deauth lists: 0xBAD0A173
#include <thru-sdk/c/tn_sdk_syscall.h>
#include <stdint.h>
// Invoke a calculator program with operation data
ushort calculator_program_idx = 5;
// Prepare instruction data
struct calc_instruction {
uint32_t operation; // 1 = add, 2 = multiply
uint64_t operand_a;
uint64_t operand_b;
} instruction = {
.operation = 1, // Addition
.operand_a = 100,
.operand_b = 200
};
ulong invoke_err = (ulong)-1;
ulong result = tsys_invoke( &instruction,
sizeof(instruction),
calculator_program_idx,
NULL,
&invoke_err );
if (result == TN_VM_SYSCALL_SUCCESS && invoke_err == TN_VM_SYSCALL_SUCCESS) {
// Calculator program is now executing
// It will receive instruction data in a0, a1
// Current program state saved on shadow stack
// Execution will continue in calculator program
}