Skip to main content
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 intN uses full 257-bit precision, so any integer value fits into it. Overflow occurs only during serialization.

bool

  • The bool type is backed by TVM INT with value -1 or 0 at runtime.
  • The unsafe cast someBool as int is valid and produces -1 or 0.

address, any_address

  • Both address and any_address are backed by TVM SLICE values containing raw binary data.
  • A nullable address? is represented as either TVM NULL or SLICE.
  • The unsafe cast someAddr as slice is valid and reversible.

cell

  • The cell type is backed by TVM CELL.
  • The unsafe cast someCell as Cell<T> is valid.

Cell<T>

  • The Cell<T> type is also backed by TVM CELL. The type parameter T is compile‑time metadata.

slice

  • Type slice is backed by TVM SLICE.

bitsN

  • The bitsN type is backed by TVM SLICE.
  • The unsafe cast someSlice as bitsN is valid and reversible.

RemainingBitsAndRefs

  • The RemainingBitsAndRefs type is backed TVM SLICE. It is an alias of slice that is handled specially during deserialization.

builder

  • The builder type is backed by TVM BUILDER. Bits written to a builder cannot be read directly. Access to the data is possible only by converting the builder to a slice.

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 for int;
  • the union occupies N+1 stack slots, where N is the maximum size of T_i;
  • the(N+1)-th slot contains the typeid of 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
}

tuple

  • Type tuple is backed by TVM TUPLE and occupies a single stack slot, regardless of the number of elements; up to 255.

Typed tuple [T1, T2, ...]

A typed tuple is backed by TVM TUPLE. Its structure is known at compile time; at runtime it is an ordinary TVM tuple.
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>

Callables (...ArgsT) -> ResultT

  • A callable and continuation is backed by TVM CONT.

void, never

  • Both represent the absence of a value and occupy zero stack slots. For example, a function with return type void does not place any value onto the stack.