Step 1 of 3 · Reading · ~3 min
Representing Values in the VM
Bytecode Virtual Machine
Values Aren't Just Bytecode Constants
Your VM's bytecode stream stores raw bytes — opcodes and operand indices. But
the values those instructions produce and consume (numbers, booleans,
strings, nil) need a runtime representation that the interpreter loop can
inspect quickly: "is this a number?", "are these two values equal?", "print
this value."
That representation is a tagged union (sometimes called a tagged
variant or discriminated union): every Value carries a small tag saying
what kind of thing it is, plus the payload for that kind.
Value = (tag, payload)
tag = NIL | BOOL | NUMBER | OBJ
A minimal encoding looks like:
Real VMs (like clox) pack this more efficiently — a C struct with a
ValueType enum and a union of double/bool/Obj*, or even NaN-boxing
where every value is a single 64-bit float and non-numbers are hidden inside
NaN bit patterns. The tagged tuple above is the same idea at the source
level: cheap to test, cheap to unwrap.
Truthiness
Only nil and false are "falsey" — every other value, including 0 and
"", is truthy. This is the rule OP_JUMP_IF_FALSE and ! will rely on
later, so get it right now:
Strings Are Heap Objects
Numbers and booleans are small enough to copy by value. Strings are not — they're heap-allocated objects. This distinction matters for two reasons you'll meet again soon:
- Equality. Two number values with the same bits are equal. Two string objects holding the same characters should also compare equal — but if your VM later interns strings (deduplicates identical literals into one heap object), equality becomes a cheap pointer comparison instead of a character-by-character scan.
- Garbage collection. Every object you allocate on the heap is something the collector must eventually be able to find and free. By modeling strings as heap objects now — rather than raw host-language strings — you're building the exact hook the mark-and-sweep collector (a few chapters ahead) will attach to. Keep strings immutable: once created, a string's contents never change. That sidesteps a whole class of aliasing bugs and makes interning safe.
Concatenation and Printing
OP_CONCAT pops two values, requires both to be strings (or coerces, if
your language allows 1 + "x"-style mixing — decide that now and be
consistent), and pushes a new heap string. OP_ADD on two numbers is
separate: don't let + silently do both, or your runtime type errors get
mushy. Finally, every value needs a stringify for print:
What to build
Add NIL/TRUE/FALSE opcodes and a tagged Value type to your VM.
Implement OP_ADD for numbers, OP_CONCAT (or an overloaded +) for
strings, OP_EQUAL that compares by tag-then-value, and stringify for
print. Watch the edge cases: comparing a number to a string should be
false, not a crash; concatenating nil should be a runtime error with a
clear message, not a Python TypeError leaking through.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…