About SUI Move

Sui Move Execution Model

Runtime-oriented documentation for how latest Sui Move executes bytecode: runtime and VM construction, linkage and dispatch, frame and stack semantics, type encoding, runtime checks, gas charging, and storage rebates.

Introduction

What is the MoveVM?

In latest Sui, MoveRuntime owns the package cache, native registry, and VM configuration, and builds a linkage-specific MoveVM for execution. That MoveVM executes compiled Move bytecode, mutates stack/frame/value state, invokes native functions, and returns either success values or a typed failure status.

What is a package and its modules?

A package is an on-chain unit containing compiled modules plus linkage metadata. Each module has function definitions and module tables (handles, signatures, constants, and definitions). In latest Sui, cross-package execution is resolved against a LinkageContext and per-linkage VMDispatchTables, so dispatch is package- and linkage-aware rather than only a raw handle lookup.

Execution Environment

Execution Stages

Sui starts from a programmable transaction command stream. The adapter prepares input objects, gas state, linkage, and native extensions, then asks MoveRuntime to build or reuse a linkage-specific MoveVM. A MoveCall executes bytecode opcode-by-opcode; the serialized call opcodes stay CALL and CALL_GENERIC, but the latest runtime lowers them internally into direct, virtual, or generic dispatch forms. Native boundaries handle object runtime, transfers, and TxContext operations before Sui finalizes effects and gas.

Data Regions

RegionScope / LifetimeMain Access Path
Function opcode streamFrame-local (current function)Program counter + branch/call/return opcodes
Function jump tablesFrame-local (enum-capable functions)VARIANT_SWITCH targets function-local jump tables
Module tablesModule/package metadataIndices in opcodes such as CALL, LD_CONST, PACK, FIELD ops
Linkage / dispatch tablesVM-instance shared for one linkage contextLinkageContext + VMDispatchTables resolve packages, functions, and datatypes
Program counterFrame-localAdvanced or redirected by control-flow opcodes
Operand stackInterpreter-shared across active framesMost opcodes consume/produce values here
Call stackInterpreter-sharedCALL/CALL_GENERIC push, RET pops
Machine heap / localsInterpreter-managed frame storageFrame allocation plus local-register access through COPY_LOC, MOVE_LOC, ST_LOC, *_BORROW_LOC
Base heapInvocation-level external value storageHolds PTB / external arguments so references into those values can exist
Type argumentsFrame-local metadataGeneric instantiation resolution, not runtime stack data
Sui native stateTransaction-level shared object/package stateNative extensions such as ObjectRuntime, TransactionContext, and native cost tables

Latest runtime limits are 1024 operand-stack entries, 1024 call frames, 2048 locals per frame, type depth 256, and value depth 128.

Runtime values are represented as primitives, vectors or primitive vectors, structs, variants, and references. References are either direct references to a value cell or indexed references into an aggregate value.

Function Opcode Stream and Program Counter

Each frame executes one function bytecode stream. The program counter (PC) points to opcode position, not a raw hex byte offset. BRANCH, BR_TRUE, and BR_FALSE set PC to target opcode position. VARIANT_SWITCH reads its branch destinations from the function's local jump-table vector. If PC moves beyond available opcode positions, execution fails with PC_OVERFLOW.

Module Tables Context

Tables are immutable metadata for the active module context. Serialized opcodes still read table entries by index: LD_CONST resolves constants, and type/field/struct/variant opcodes resolve layouts through signature tokens and handle tables. In the latest runtime, many of those indices are translated up front into cached pointers or vtable keys. In particular, serialized CALL can become internal DirectCall or VirtualCall, while serialized CALL_GENERIC becomes internal generic-call dispatch.

Frame Lifecycle

On function entry, the VM allocates a stack frame on the runtime machine heap, sizes locals to the function local count, and initializes argument locals from stack values. For calls, argument values are popped from the shared operand stack, written into callee locals, and a new frame starts at pc = 0. Invocation/PTB values that need borrowable storage live in the base heap. On return, the frame is freed and control resumes in the caller at the instruction after the originating call site.

Operand Stack Semantics

The operand stack is shared across active call frames. Arithmetic, loads, comparisons, casts, pack/unpack, and reference operations all read/write this stack. CALL consumes callee arguments from this shared stack. Return values are left on the shared stack for the caller. The latest runtime caps this stack at 1024 values.

RET Semantics

RET ends the current frame. The number of return values is inferred from the callee function signature, not by “top-most only” convention. Those values remain on the shared operand stack where the caller continues execution, regardless of whether the runtime lowered the original call to direct or virtual dispatch.

Locals Lifecycle

Locals live inside stack frames allocated on the machine heap. Arguments are loaded first into loc[0..arg_count-1] in parameter order. Remaining locals are initialized as invalid until set. Reading/moving invalid locals or violating local access constraints causes runtime failure statuses. The latest runtime caps locals at 2048 per frame.

Type Arguments

Type arguments are frame metadata, not runtime values in locals or operand stack. Generic opcodes and handle resolution read this metadata to instantiate concrete types for signatures, datatype layouts, field accesses, and function instantiations. In latest Sui, that instantiation happens against the active linkage context and dispatch tables.

Move Types and SignatureToken Encoding

Move type information is encoded through signature vectors. A signature encodes ULEB128 length followed by SignatureToken bytes. Composite tokens recursively append nested token streams, and datatype-based tokens resolve through table indices rather than inline type names. In the latest binary-format source the underlying token names are Datatype and DatatypeInstantiation; older docs often call them STRUCT and STRUCT_INST.

ByteTokenEncoding Detail
0x1BOOLBoolean primitive
0x2U88-bit unsigned integer
0x3U6464-bit unsigned integer
0x4U128128-bit unsigned integer
0x5ADDRESSAccount address type
0x6REFERENCEFollowed by nested SignatureToken
0x7MUTABLE_REFERENCEFollowed by nested SignatureToken
0x8DATATYPE (historically STRUCT)Followed by ULEB128 index into STRUCT_HANDLES; in latest source this handle space is named DATATYPE_HANDLES
0x9TYPE_PARAMETERFollowed by ULEB128 type parameter index
0xAVECTORFollowed by nested SignatureToken element type
0xBDATATYPE_INST (historically STRUCT_INST)Datatype handle index + type substitution signature vector
0xCSIGNERSigner special type
0xDU1616-bit unsigned integer
0xEU3232-bit unsigned integer
0xFU256256-bit unsigned integer
Example A: (u8, u128)                   -> 0x02 0x02 0x04
Example B: (vector<address>)            -> 0x01 0x0A 0x05
Example C: (&Datatype(handle=0x10))     -> 0x01 0x06 0x08 0x10
Example D: (Datatype<T0>) via DATATYPE_INST
          -> 0x01 0x0B 0x10 0x01 0x09 0x00

Latest source defines bytecode versions 1 through 7, while the current serializer emits versions 5 through 7. Version 4 adds vector opcodes, version 6 adds U16/U32/U256 tokens and opcodes, and version 7 adds enums plus function-local jump tables. Version 7 and above must carry the Sui binary flavor.

Verifier vs Runtime Checks

Before execution, deserialization and bytecode verification enforce binary-version gates, table/opcode validity, well-formed signatures, control flow, stack usage consistency, reference safety constraints, and dependency compatibility. Sui then applies extra verifier rules, including rejecting Move global-storage opcodes because object storage is handled by the adapter. Runtime still enforces dynamic checks during execution: stack underflow/overflow, type and layout validity, arithmetic failures, bounds checks, depth limits, and invariant checks.

Runtime Error Taxonomy

StatusTypical Trigger
ABORTEDABORT opcode with u64 abort code
EMPTY_VALUE_STACKPop/read when stack has insufficient values
EXECUTION_STACK_OVERFLOWPushing past the operand-stack limit
CALL_STACK_OVERFLOWPushing past the call-stack limit
PC_OVERFLOWProgram counter moved beyond opcode range
INTERNAL_TYPE_ERRORType mismatch for opcode operand expectations
VECTOR_OPERATION_ERRORVector index, length, or parity violations
VARIANT_TAG_MISMATCHUnpack/switch expects a different enum variant tag
VM_MAX_VALUE_DEPTH_REACHEDConstructed/runtime value exceeds depth limit
UNKNOWN_INVARIANT_VIOLATION_ERRORInternal invariant failure

Sui Native Boundary

Move bytecode execution can invoke native functions that bridge into Sui runtime behavior. Latest Sui installs native extensions such as ObjectRuntime, TransactionContext, and the native cost table. The adapter uses that boundary for object operations, transfer flows, fresh IDs, event/object effect materialization, and final transaction results.

Gas Costs

VM Instruction Metering

During execution the gas meter charges instruction costs, reference/value-size related costs, and native-call charging. Metering is continuous in the interpreter loop and can halt execution on out-of-gas.

Adapter-Level Charging

Sui adapter gas charging performs transaction-level accounting outside raw bytecode stepping: publish/upgrade charging, storage read/write accounting, gas smashing handling, and final net gas deduction against gas payment state.

Gas Refunds

Storage Rebate Timing

Storage rebate is computed after command execution from concrete object-state deltas and storage accounting. Rebate is not known a priori during bytecode stepping; it is applied in final charging/finalization.

Net Gas Finalization

Final net gas is derived from computation + storage costs minus rebates. On storage charging failures/out-of-gas branches, adapter logic applies dedicated fallback handling before producing final transaction effects.