Bytecode Type System
Move bytecode represents every type as a signature token — a tagged, recursive data structure stored in the Signature table. This page documents how types are encoded at the binary level, how abilities constrain them, and how the handle indirection model connects types to their definitions.
Signature Tokens
Section titled “Signature Tokens”A SignatureToken is the core type representation in Move bytecode. Each token starts with a single
tag byte that identifies the kind of type, optionally followed by additional data (an inner token,
an index, or a list of type arguments).
Quick Reference Table
Section titled “Quick Reference Table”| Token | Tag | Additional Data | Version |
|---|---|---|---|
Bool | 0x01 | — | v5+ |
U8 | 0x02 | — | v5+ |
U64 | 0x03 | — | v5+ |
U128 | 0x04 | — | v5+ |
Address | 0x05 | — | v5+ |
Reference | 0x06 | inner SignatureToken | v5+ |
MutableReference | 0x07 | inner SignatureToken | v5+ |
Struct | 0x08 | StructHandleIndex (ULEB128) | v5+ |
TypeParameter | 0x09 | u16 index (ULEB128) | v5+ |
Vector | 0x0A | inner SignatureToken | v5+ |
StructInstantiation | 0x0B | StructHandleIndex + count + type arg tokens | v5+ |
Signer | 0x0C | — | v5+ |
U16 | 0x0D | — | v6+ |
U32 | 0x0E | — | v6+ |
U256 | 0x0F | — | v6+ |
Function | 0x10 | param tokens + return tokens + ability mask | v8+ |
I8 | 0x11 | — | v9+ |
I16 | 0x12 | — | v9+ |
I32 | 0x13 | — | v9+ |
I64 | 0x14 | — | v9+ |
I128 | 0x15 | — | v9+ |
I256 | 0x16 | — | v9+ |
Primitive Tokens
Section titled “Primitive Tokens”Primitive tokens carry no additional data — the tag byte alone identifies the type.
Unsigned integers: Bool (0x01), U8 (0x02), U64 (0x03), U128 (0x04), U16 (0x0D),
U32 (0x0E), U256 (0x0F). All unsigned integer types have the abilities copy, drop, and
store.
Signed integers (v9+): I8 (0x11), I16 (0x12), I32 (0x13), I64 (0x14), I128 (0x15),
I256 (0x16). These share the same abilities as their unsigned counterparts.
Special types: Address (0x05) has copy + drop + store. Signer (0x0C) has only drop
(it cannot be copied or stored).
Compound Tokens
Section titled “Compound Tokens”Vector (0x0A) is followed by a single inner SignatureToken representing the element type. For
example, vector<u64> serializes as 0x0A 0x03 — the Vector tag followed by the U64 tag.
Vector inherits copy, drop, and store from its element type.
Struct (0x08) is followed by a ULEB128-encoded StructHandleIndex that points into the
StructHandle table. This is used for non-generic struct types (or generic structs whose type
parameters are not instantiated at this usage site).
StructInstantiation (0x0B) represents a generic struct with concrete type arguments. It is followed by:
- A ULEB128-encoded
StructHandleIndex - A ULEB128-encoded count of type arguments
- That many
SignatureTokenvalues, one per type argument
For example, Table<address, u64> would serialize as 0x0B <handle_idx> 0x02 0x05 0x03.
Reference Tokens
Section titled “Reference Tokens”Reference (0x06) and MutableReference (0x07) each wrap a single inner SignatureToken.
References always have copy + drop abilities. They cannot appear inside structs — the verifier
rejects any struct field with a reference type.
Generic Type Parameter Token
Section titled “Generic Type Parameter Token”TypeParameter (0x09) is followed by a ULEB128-encoded u16 index that refers to a type
parameter of the enclosing generic struct or function. For example, in a function
fun foo<T, U>(x: T), the parameter T appears as 0x09 0x00 (type parameter index 0) and
U would be 0x09 0x01 (type parameter index 1).
Function Token (v8+)
Section titled “Function Token (v8+)”Function (0x10) represents a first-class function type (used with closures). It serializes as:
- A ULEB128-encoded count of parameter types
- That many
SignatureTokenvalues for the parameters - A ULEB128-encoded count of return types
- That many
SignatureTokenvalues for the returns - A
u8ability bitmask (see Abilities)
Signature Token Depth
Section titled “Signature Token Depth”The VM enforces a maximum nesting depth of 256 for signature tokens. Deeply nested types such as
vector<vector<vector<...>>> are rejected during deserialization if they exceed this limit.
Abilities
Section titled “Abilities”Move uses four abilities to control what operations a type supports. Abilities are encoded as a
u8 bitmask, where each ability occupies a single bit position.
Ability Bit Values
Section titled “Ability Bit Values”| Ability | Bit Value | Description |
|---|---|---|
copy | 0x01 | Values can be duplicated (via CopyLoc, ReadRef) |
drop | 0x02 | Values can be discarded (via Pop, WriteRef, StLoc, leaving scope) |
store | 0x04 | Values can exist inside a struct in global storage |
key | 0x08 | The type can serve as a top-level key for global storage operations |
The bitmask is the bitwise OR of the individual ability values. For example, copy + drop + store
encodes as 0x01 | 0x02 | 0x04 = 0x07. The empty set is 0x00.
Common Ability Sets
Section titled “Common Ability Sets”| Set Name | Abilities | Bitmask | Used By |
|---|---|---|---|
EMPTY | (none) | 0x00 | — |
PRIMITIVES | copy + drop + store | 0x07 | Bool, U8, U64, U128, Address, integers |
SIGNER | drop | 0x02 | Signer |
REFERENCES | copy + drop | 0x03 | Reference, MutableReference |
FUNCTIONS | drop | 0x02 | Minimum for function types |
ALL | copy + drop + store + key | 0x0F | — |
How Abilities Constrain Instructions
Section titled “How Abilities Constrain Instructions”The bytecode verifier checks abilities before allowing certain instructions:
CopyLocandReadRefrequire the type to havecopy.Pop,WriteRef,StLoc(when overwriting), andEq/Neqrequiredrop. A value left in a local whenRetexecutes also requiresdrop.MoveTorequires the type to havekey.MoveFrom,BorrowGlobal,BorrowGlobalMut, andExistsalso requirekey.- Fields of a struct with
keymust havestore(since they reside in global storage).
Ability Requirements for Generics
Section titled “Ability Requirements for Generics”When a generic struct S<T> declares has copy, drop, the type parameter T must satisfy certain
ability constraints for the instantiation S<ConcreteType> to have those abilities. The rule is:
- For
S<T>to have abilitya, every non-phantom type parameterTmust havea.requires(). - The
requiresmapping is:copyrequirescopy,droprequiresdrop,storerequiresstore, andkeyrequiresstore.
These constraints are stored in the StructTypeParameter struct, which pairs each type parameter
with an AbilitySet of constraints and a is_phantom flag.
Phantom Type Parameters
Section titled “Phantom Type Parameters”A type parameter declared as phantom does not contribute to the ability requirements of the
struct. A phantom parameter does not appear in any field of the struct — it exists only as a type
tag. For example, in struct Coin<phantom T> has store { value: u64 }, the type T carries no
ability constraints because it is phantom.
At the bytecode level, StructTypeParameter records is_phantom: true for phantom parameters.
The verifier confirms that phantom parameters are never used in non-phantom positions within
field types.
Handle Indirection Model
Section titled “Handle Indirection Model”Move bytecode does not inline type names, addresses, or signatures directly into instructions. Instead, it uses a system of indices that point into shared tables. This indirection provides compact binary encoding, deduplication of repeated references, and efficient module loading.
Index Types
Section titled “Index Types”| Index Type | Points To | Rust Type |
|---|---|---|
ModuleHandleIndex | Module handle table | u16 |
StructHandleIndex | Struct handle table | u16 |
FunctionHandleIndex | Function handle table | u16 |
FieldHandleIndex | Field handle table | u16 |
SignatureIndex | Signature table | u16 |
IdentifierIndex | Identifier (string) table | u16 |
AddressIdentifierIndex | Address table | u16 |
ConstantPoolIndex | Constant pool | u16 |
StructDefinitionIndex | Struct definition table | u16 |
FunctionDefinitionIndex | Function definition table | u16 |
StructDefInstantiationIndex | Struct instantiation table | u16 |
FunctionInstantiationIndex | Function instantiation table | u16 |
FieldInstantiationIndex | Field instantiation table | u16 |
All index types are u16 values (maximum 65,535 entries per table). They are serialized as
ULEB128 in the binary format.
Why Indices Instead of Inline Data
Section titled “Why Indices Instead of Inline Data”- Compact binary size. A function might reference the same struct type dozens of times. With indices, each reference is a 1—2 byte ULEB128 value instead of a full module address + name string.
- Deduplication. Identical signatures, identifiers, and addresses are stored once and referenced by index. The serializer ensures no duplicate entries exist in any table.
- Efficient loading. The VM can resolve handles once during module loading and cache the results. Instructions then operate on pre-resolved data.
Indirection Chain Example
Section titled “Indirection Chain Example”Consider the instruction Call(FunctionHandleIndex(3)). The VM resolves the call target through
a chain of table lookups:
Instruction: Call(FunctionHandleIndex(3)) | vFunctionHandle #3: module: ModuleHandleIndex(0) ----> ModuleHandle #0: name: IdentifierIndex(5) address: AddressIdentifierIndex(1) -> 0x1 parameters: SignatureIndex(2) name: IdentifierIndex(2) -> "vector" return_: SignatureIndex(1) | v IdentifierIndex(5) -> "push_back" SignatureIndex(2) -> [Vector(TypeParameter(0)), TypeParameter(0)] SignatureIndex(1) -> []The VM chains through: instruction operand to function handle to module handle (and from there to the account address and module name), identifier table (for the function name), and signature table (for parameter and return types). Each step is an array lookup by index.
Struct Handle Resolution
Section titled “Struct Handle Resolution”The same pattern applies to type resolution. A Struct(StructHandleIndex(2)) signature token
resolves through:
SignatureToken: Struct(StructHandleIndex(2)) | vStructHandle #2: module: ModuleHandleIndex(1) -> address + module name name: IdentifierIndex(4) -> "Coin" abilities: 0x07 -> copy + drop + store type_parameters: [] -> (non-generic)Generics at the Bytecode Level
Section titled “Generics at the Bytecode Level”Move supports generic (parameterized) types and functions. At the bytecode level, generics use type parameter indices and instantiation tables to avoid duplicating definitions for each concrete type.
Type Parameter Representation
Section titled “Type Parameter Representation”Type parameters are represented as u16 indices (TypeParameterIndex). Inside a generic function
or struct definition, references to type parameters use the TypeParameter(index) signature
token. Index 0 is the first type parameter, index 1 is the second, and so on.
- In a struct handle, type parameters are stored as a
Vec<StructTypeParameter>, where each entry carries ability constraints and a phantom flag. - In a function handle, type parameters are stored as a
Vec<AbilitySet>, listing the required abilities for each type parameter.
Instantiation Tables
Section titled “Instantiation Tables”When generic functions or structs are used with concrete type arguments, the bytecode stores the instantiation in a separate table rather than duplicating the handle.
FunctionInstantiation pairs a FunctionHandleIndex with a SignatureIndex that holds the
concrete type arguments:
| Field | Type | Description |
|---|---|---|
handle | FunctionHandleIndex | The generic function being instantiated |
type_parameters | SignatureIndex | Index into Signature table holding type args |
StructDefInstantiation pairs a StructDefinitionIndex with a SignatureIndex:
| Field | Type | Description |
|---|---|---|
def | StructDefinitionIndex | The generic struct definition |
type_parameters | SignatureIndex | Index into Signature table holding type args |
FieldInstantiation pairs a FieldHandleIndex with a SignatureIndex:
| Field | Type | Description |
|---|---|---|
handle | FieldHandleIndex | The field in a generic struct |
type_parameters | SignatureIndex | Index into Signature table holding type args |
Generic Instructions
Section titled “Generic Instructions”Instructions that operate on generic types or functions come in paired forms: a base instruction
and a *Generic variant. The base instruction uses a direct handle or definition index, while the
generic variant uses an instantiation index.
| Base Instruction | Generic Variant | Operand Type |
|---|---|---|
Call | CallGeneric | FunctionInstantiationIndex |
Pack | PackGeneric | StructDefInstantiationIndex |
Unpack | UnpackGeneric | StructDefInstantiationIndex |
Exists | ExistsGeneric | StructDefInstantiationIndex |
MoveFrom | MoveFromGeneric | StructDefInstantiationIndex |
MoveTo | MoveToGeneric | StructDefInstantiationIndex |
ImmBorrowGlobal | ImmBorrowGlobalGeneric | StructDefInstantiationIndex |
MutBorrowGlobal | MutBorrowGlobalGeneric | StructDefInstantiationIndex |
ImmBorrowField | ImmBorrowFieldGeneric | FieldInstantiationIndex |
MutBorrowField | MutBorrowFieldGeneric | FieldInstantiationIndex |
Worked Example: vector::push_back<u64>(v, 42)
Section titled “Worked Example: vector::push_back<u64>(v, 42)”This call to a generic function compiles to a CallGeneric instruction. Here is the resolution
chain:
- The compiler emits
CallGeneric(FunctionInstantiationIndex(N)). FunctionInstantiation #Ncontains:handle:FunctionHandleIndex(M)(pointing to thepush_backfunction handle)type_parameters:SignatureIndex(K)(pointing to a signature containing[U64])
FunctionHandle #Mcontains:module:ModuleHandleIndexpointing to0x1::vectorname:IdentifierIndexpointing to"push_back"parameters:SignatureIndexpointing to[Vector(TypeParameter(0)), TypeParameter(0)]return_:SignatureIndexpointing to[]type_parameters:[AbilitySet::EMPTY](no constraints onT)
- At runtime, the VM substitutes
TypeParameter(0)withU64from the instantiation, yielding effective parameter types[vector<u64>, u64].
Function Types (v8+)
Section titled “Function Types (v8+)”Bytecode version 8 introduced first-class function types to support closures. A function type describes the signature of a callable value — its parameter types, return types, and the abilities the closure must satisfy.
The Function Signature Token
Section titled “The Function Signature Token”The Function signature token (tag 0x10) carries:
| Component | Encoding |
|---|---|
| Parameter count | ULEB128 |
| Parameter types | Sequence of SignatureToken values |
| Return count | ULEB128 |
| Return types | Sequence of SignatureToken values |
| Abilities | u8 bitmask |
For example, a function type |u64, bool| -> address with drop ability serializes as:
0x10 0x02 0x03 0x01 0x01 0x05 0x02 — Function tag, 2 params, U64, Bool, 1 return, Address,
drop bitmask (0x02).
All function types have at least the drop ability. Public functions also get copy and store.
Private functions get copy and drop but not store (since they may not be valid after a module
upgrade).
Closure Instructions
Section titled “Closure Instructions”Three instructions work with function types:
-
PackClosure(FunctionHandleIndex, ClosureMask)(opcode0x58) — Creates a closure by capturing some arguments of the named function. TheClosureMaskis au64bitmask indicating which parameters are captured from the stack (bit set = captured). The remaining parameters become the closure’s parameter types. -
PackClosureGeneric(FunctionInstantiationIndex, ClosureMask)(opcode0x59) — Same asPackClosurebut for a generic function instantiation. -
CallClosure(SignatureIndex)(opcode0x5A) — Invokes a closure. TheSignatureIndexdescribes the expected function type. The closure value sits on top of the stack, with the remaining (non-captured) arguments below it.
Brief Example
Section titled “Brief Example”Given a function fun add(x: u64, y: u64): u64, creating and calling a closure that captures
the first argument:
// Capture x=5, leaving a closure of type |u64| -> u64LdU64(5)PackClosure(FunctionHandleIndex(add), mask=0b01)// Stack: [closure]
// Call the closure with y=10LdU64(10)CallClosure(SignatureIndex(|u64| -> u64))// Stack: [15]The mask 0b01 means “capture parameter 0.” The resulting closure takes one remaining argument
(parameter 1) and returns u64.
Struct Definitions
Section titled “Struct Definitions”A struct (or enum) type in Move bytecode is split across two layers: a handle that describes the type’s identity and a definition that provides its fields.
StructHandle
Section titled “StructHandle”The StructHandle describes a struct type’s public interface:
| Field | Type | Description |
|---|---|---|
module | ModuleHandleIndex | The module that defines this type |
name | IdentifierIndex | The name of the type |
abilities | AbilitySet (u8) | Declared abilities of the type |
type_parameters | Vec<StructTypeParameter> | Type parameter constraints and phantom flags |
Each StructTypeParameter consists of:
| Field | Type | Description |
|---|---|---|
constraints | AbilitySet | Abilities required of the type argument |
is_phantom | bool | Whether the parameter is phantom |
StructDefinition
Section titled “StructDefinition”The StructDefinition connects a handle to the type’s field layout:
| Field | Type | Description |
|---|---|---|
struct_handle | StructHandleIndex | Points to the corresponding StructHandle |
field_information | StructFieldInformation | Field layout (native, declared, or variants) |
StructFieldInformation is an enum with three variants:
| Variant | Serialization Tag | Content |
|---|---|---|
Native | 0x01 | No fields (native type) |
Declared | 0x02 | List of FieldDefinition entries |
DeclaredVariants | 0x03 | List of VariantDefinition entries (v7+) |
FieldDefinition
Section titled “FieldDefinition”Each field in a struct is represented by:
| Field | Type | Description |
|---|---|---|
name | IdentifierIndex | Name of the field |
signature | TypeSignature | The field’s type (a SignatureToken) |
Variant Struct Definitions (v7+)
Section titled “Variant Struct Definitions (v7+)”Bytecode version 7 added enum types using the DeclaredVariants field information. Each
VariantDefinition contains:
| Field | Type | Description |
|---|---|---|
name | IdentifierIndex | Name of the variant |
fields | Vec<FieldDefinition> | Fields specific to this variant |
The variant index is a u16 value determined by the variant’s position in the list. Additional
tables support variant operations:
- StructVariantHandle — pairs a
StructDefinitionIndexwith aVariantIndexto identify a specific variant. - StructVariantInstantiation — pairs a
StructVariantHandleIndexwith aSignatureIndexfor generic variant operations. - VariantFieldHandle — identifies a field shared across multiple variants of the same enum.
- VariantFieldInstantiation — generic version of
VariantFieldHandle.
Visibility and Access
Section titled “Visibility and Access”Function visibility and access control are encoded directly in the bytecode, governing who can call a function and what resources it may touch.
Visibility Flags
Section titled “Visibility Flags”The Visibility enum is stored as a u8 in each FunctionDefinition:
| Visibility | Value | Description |
|---|---|---|
Private | 0x00 | Callable only within the defining module |
Public | 0x01 | Callable from any module or script |
Friend | 0x03 | Callable from the defining module and declared friend modules |
Value 0x02 was previously used for Script visibility but is now deprecated in favor of the
separate entry modifier.
Entry Functions
Section titled “Entry Functions”The is_entry flag (serialized as a u8 where 0x04 represents the entry bit) marks a function
as a valid transaction entry point. Entry functions can be called directly by the Aptos
transaction runtime. A function can be both public and entry, or private and entry.
Access Specifiers (v7+)
Section titled “Access Specifiers (v7+)”Bytecode version 7 added access specifiers to function handles. An access specifier describes which global resources a function reads or writes, enabling static analysis of a function’s storage footprint.
Each AccessSpecifier contains:
| Field | Type | Description |
|---|---|---|
kind | AccessKind | Reads (0x01) or Writes (0x02) |
negated | bool | Whether the specifier is negated |
resource | ResourceSpecifier | Which resource(s) are accessed |
address | AddressSpecifier | At which address(es) |
ResourceSpecifier variants:
| Variant | Tag | Description |
|---|---|---|
Any | 0x01 | Any resource |
DeclaredAtAddress | 0x02 | Resources declared at a specific address |
DeclaredInModule | 0x03 | Resources declared in a specific module |
Resource | 0x04 | A specific struct type |
ResourceInstantiation | 0x05 | A specific generic struct instantiation |
AddressSpecifier variants:
| Variant | Tag | Description |
|---|---|---|
Any | 0x01 | Any storage address |
Literal | 0x02 | A specific literal address |
Parameter | 0x03 | Derived from a function parameter (optionally via a known function like object::address_of) |
If a function handle has no access specifiers (None), the VM assumes the function may access
arbitrary resources. An empty specifier list (Some([])) indicates a pure function with no global
storage dependencies.
Further Reading
Section titled “Further Reading”- Module Binary Format — serialization layout of each table referenced in this page
- Bytecode Version History — which version introduced each signature token, instruction, and table type