TVM stack representation
This page assumes prior knowledge of the Tolk type system and TVM. It serves as a concise low-level reference.
A consolidated summary of how Tolk types are represented on the TVM stack.
int, intN, coins
- All numeric types are backed by TVM
INT. - Type
intNuses full 257-bit precision, so any integer value fits into it. Overflow occurs only during serialization.
bool
- The
booltype is backed by TVMINTwith value-1or0at runtime. - The unsafe cast
someBool as intis valid and produces-1or0.
address, any_address
- Both
addressandany_addressare backed by TVMSLICEvalues containing raw binary data. - A nullable
address?is represented as either TVMNULLorSLICE. - The unsafe cast
someAddr as sliceis valid and reversible.
cell
- The
celltype is backed by TVMCELL. - The unsafe cast
someCell as Cell<T>is valid.
Cell<T>
- The
Cell<T>type is also backed by TVMCELL. The type parameterTis compile‑time metadata.
slice
- Type
sliceis backed by TVMSLICE.
bitsN
- The
bitsNtype is backed by TVMSLICE. - The unsafe cast
someSlice as bitsNis valid and reversible.
RemainingBitsAndRefs
- The
RemainingBitsAndRefstype is backed TVMSLICE. It is an alias ofslicethat is handled specially during deserialization.
builder
- The
buildertype is backed by TVMBUILDER. Bits written to a builder cannot be read directly. Access to the data is possible only by converting thebuilderto aslice.
struct
Fields of a structure are placed sequentially on the stack. For example, Point occupies two stack slots, and Line occupies four:
struct Point {
x: int
y: int
}
struct Line {
start: Point
end: Point
}When constructing a Line value, four integers are placed onto the stack:
fun generateLine() {
val l: Line = {
start: { x: 10, y: 20 },
end: { x: 30, y: 40 },
};
// on a stack: 10 20 30 40
return l;
}Single-field structures have no overhead compared to the plain values.
enum
- Every enum is backed by TVM
INT. Tolk supports integer enums only; for example, not addresses.
Nullable types T?
Primitive nullable types such as int?, address?, and cell? occupy a single stack slot. That slot holds either TVM NULL or the corresponding value.
fun demo(maybeAddr: address?) {
// maybeAddr is one stack slot: `NULL` or `SLICE`
}A nullable structure with a single non-nullable primitive field can also be represented:
struct MyId {
value: int32
}
fun demo(maybeId: MyId?) {
// maybeId is one stack slot: `NULL` or `INT`
}Nullable values of multi-slot types, such as Point or a tensor (bool, cell), occupy N + 1 stack slots. The last is used for "typeid".
struct Point {
x: int
y: int
}
fun demo(maybeP: Point?) {
// maybeP is 3 stack slots:
// when null: "NULL, NULL, INT (0)"
// when not: "INT (x), INT (y), INT (4567)"
}For every nullable type, the compiler assigns a unique typeid; for example, 4567 for Point. The typeid is stored in an extra stack slot. The typeid value for null is 0. Expressions such as p == null or p is Point check the typeid slot.
The following structure, when nullable, requires an extra stack slot:
struct Tricky {
opt: int?
}
fun demo(v: Tricky?) {
// v occupies 2 stack slots,
// because either `v == null` or `v.opt == null`:
// when v == null: "NULL, INT (0)"
// when v != null: "INT/NULL, INT (2345)"
}Union types T1 | T2 | ...
Union types are represented as tagged unions on the stack:
- each alternative type is assigned a unique
typeid; e.g., 1234 forint; - the union occupies N+1 stack slots, where N is the maximum size of
T_i; - the(N+1)-th slot contains the
typeidof the current value.
Thus, match is implemented as a comparison of the (N+1)-th slot, and passing or assigning a value involves stack rearrangement.
fun complex(v: int | slice | (int, int)) {
// `v` is 3 stack slots:
// - int: (NULL, 100, 1234)
// - slice: (NULL, CS{...}, 2345)
// - (int, int): (200, 300, 3456)
}
fun demo(someOf: int | slice) {
// `someOf` is 2 stack slots: value and type-id
// - int: (100, 1234)
// - slice: (CS{...}, 2345)
match (someOf) {
int => { // IF TOP == 1234
// slot1 is TVM `INT`, can be used in arithmetics
}
slice => { // ELSE
// slot1 is TVM `SLICE`, can be used to loadInt()
}
}
complex(v); // passes (NULL, v.slot1, v.typeid)
complex(5); // passes (NULL, 5, 1234)
}T | null is called nullable and optimized for atomic types: int? uses a single slot. Non-atomics are handled generally, with typeid=0.
Tensors (T1, T2, ...)
Tensor components are placed sequentially on the stack, identical to struct fields. For example, (coins, Point, int?) occupies 4 stack slots: INT (coins), INT (p.x), INT (p.y), INT/NULL.
type MyTensor = (coins, Point, int?)
fun demo(t: MyTensor) {
// t is 4 stack slots
val p = t.1;
// p is 2 stack slots
}string
Type string is backed by the TVM CELL containing character data in snake format: a chain of cells where each cell holds a portion of the string and a reference to the next.
For example, a string "abcd" can be stored as a single chunk — cell(data: "abcd"), with 32 bits inside. The same string can also use nested chunks: cell(data: "ab", ref: cell(data: "cd")). Either way, a string is a cell that may contain chained refs.
unknown
The unknown type is an opaque type that represents a single TVM stack slot.
Any type T can be cast to unknown. When cast, primitives remain as-is, while multi-slot types are packed into a sub-tuple.
For example, 5 as unknown remains as 5 on the stack, whereas (10, 20) as unknown is converted to a 2-element tuple [10 20].
array<T> and tuple
An array of any T is backed by the TVM TUPLE and occupies a single stack slot, regardless of the number of elements within. The TVM TUPLE limit is 255 inner elements. The tuple is an alias for array<unknown>.
For example, array<int> [1,2,3] is a TVM tuple [1 2 3], whereas for array<Point> [ {x:10,y:20}, {x:30,y:40} ] each sub-element is a 2-component tuple itself: [ [10 20] [30 40] ]. Under the hood, T is converted to unknown on write and reversed back on read: points.get(0) returns {x:10,y:20}.
Shaped tuple [T1, T2, ...]
A shaped tuple is a fixed-size array backed by the TVM TUPLE, whose structure is known at compile-time:
fun demo(t: [int, [int, int]]) {
// t is one stack slot (TVM `TUPLE`)
// t.0 is TVM `INT`
// t.1 is TVM `TUPLE`
return t.1.0; // asm "1 INDEX" + "0 INDEX"
}map<K, V>
map<K, V>occupies a single stack slot: either TVMNULLorCELL.- Non-empty maps,
CELL, have a non-trivial bit-level layout.
Callables (...ArgsT) -> ResultT
- A callable and
continuationis backed by TVMCONT.
void, never
- Both represent the absence of a value and occupy zero stack slots. For example, a function with return type
voiddoes not place any value onto the stack.
Last updated on