Skip to content
Types & Value Representation
step 1/3

Reading — step 1 of 3

Representing Values in the VM

~2 min readBytecode Virtual Machine

Types & Value Representation

How we represent values in memory is one of the most consequential design decisions for a language implementation. It affects performance, memory usage, and what operations the GC must handle.

Tagged Unions

The most common approach is a tagged union — a type tag plus a union of possible values:

Value {
    type: enum { NUMBER, BOOLEAN, NIL, OBJ }
    as: union {
        number:  double
        boolean: bool
        obj:     Object*    // pointer to heap object
    }
}

Numbers, booleans, and nil are stored inline (no heap allocation). Strings, functions, and classes are heap objects accessed through a pointer.

NaN Boxing

An advanced technique: since IEEE 754 doubles have many NaN representations, we can encode non-number values inside NaN bit patterns:

Regular double:   [sign][11-bit exponent][52-bit mantissa]
NaN:              [sign][11111111111][non-zero mantissa]

The mantissa of a NaN has 52 bits of space. We use some bits for the type tag and the rest for a pointer or small value. This way, every Value is exactly 8 bytes — no tag overhead.

V8 uses a similar technique called Smi (Small Integer) tagging: pointers are tagged in their least-significant bit.

Heap Objects

Complex values live on the heap. All heap objects share a common header:

Object {
    type:  ObjType    // STRING, FUNCTION, CLOSURE, CLASS, INSTANCE
    next:  Object*    // linked list for GC to traverse all objects
}

ObjString : Object {
    length: int
    chars:  char[]
    hash:   uint32    // cached hash for interning
}

The next pointer forms an intrusive linked list of all heap objects — the GC walks this list during the sweep phase.

Strings as Objects

Strings are the first heap-allocated type we implement. Key decisions:

  • Immutable: strings cannot be changed after creation (like Python, Java, Lua)
  • Interned: each unique string exists once in a global table
  • Hash cached: the hash is computed once at creation and stored

Immutability and interning together enable O(1) string equality: just compare pointers.

How Lua Represents Values

Lua uses a tagged union where the tag is a byte and the value is a C union. Small strings (under 40 bytes) are interned; long strings are not. Lua 5.4 added an integer subtype alongside floats, controlled by a compile-time option.

Your Task

Implement a tagged union Value type with number, boolean, nil, and string. Make strings heap-allocated, immutable, and interned. Add string concatenation with OP_CONCAT.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…