HackVM
A minimal stack-based virtual machine and single-character instruction language, published as Hack VM at hacker.org, whose 28 single-character opcodes exist mainly so that puzzle solvers can hand-write tiny programs for the site's programming challenges.
Created by Adam Miller, credited as the author of Hack VM by both the Esolang wiki and Progopedia. The language and its two reference interpreters are published at hacker.org, the puzzle and challenge site the VM was built to serve
HackVM - published as Hack VM, and subtitled by its author A Virtual Machine for Hackers - is a deliberately tiny stack machine whose programs are strings of single-character instructions. Its own documentation makes no larger claim for it than the truth: it is “a tiny, trivial, virtual machine” whose “purpose is to be used as a simple execution engine that can run very simple programs.” It exists because the puzzle site hacker.org needed a target for challenges that ask you to write a program that produces this output, and a language small enough to specify in one page and implement in a hundred lines is the right size for that job.
That modesty is what makes it interesting. Most esoteric languages are built to be admired, argued about, or laughed at, and are then never used. HackVM was built to be used, by strangers, under adversarial conditions, to solve problems set by someone else. Progopedia makes exactly this point when it calls Hack VM “one of the few really used esoteric languages.”
Disambiguation
The name collides badly, and three unrelated things are routinely confused with it:
| Name | What it actually is |
|---|---|
| HackVM / Hack VM (this page) | Adam Miller’s stack machine at hacker.org, used for the site’s challenges |
| The Hack VM language | The intermediate stack language of the Hack computer from The Elements of Computing Systems (nand2tetris), an entirely separate educational platform |
| HHVM / Hack | Meta’s PHP-derived language and its virtual machine, a large production system with no connection to either of the above |
Repositories named hackvm on GitHub belong to both of the first two projects, so the name alone never identifies which machine is meant.
The Machine
The execution model fits in a paragraph. A program is a string; the index into that string is the program counter. The machine starts at index 0, executes the character it finds, advances, and repeats. It stops when the program counter reaches the end of the code, when an ! instruction executes, or when an exception is thrown.
The state is three pieces:
- An operand stack, where almost all work happens.
- A memory buffer of 16,384 cells, addressed 0 through 16383, all zero at startup unless the challenge specifies an initial state. Pre-initialised memory is how a challenge feeds input to a program - there is no read instruction.
- A call stack, used only to hold return addresses.
Every cell, on the stack or in memory, holds one signed integer. The documentation says these are “currently limited to 32 bits, but do not count on it, they could be large in future implementations” - and the Python interpreter published at hacker.org in fact bounds values at the signed 64-bit range, raising an integer overflow exception outside it. Programs that depend on wraparound, or on a specific overflow point, are depending on an implementation detail the author explicitly warned about.
The Instruction Set
The documentation gives no total; counting the ten digit pushes and the no-op space, the instruction table holds twenty-eight distinct opcode characters. S0 is the top of the stack, S1 the next, and most instructions consume the operands they read.
| Opcode | Effect |
|---|---|
| (space) | Do nothing - the only formatting tool the language has |
0-9 | Push that digit |
+ - * / | Push S1+S0, S1-S0, S1*S0, S1/S0 |
: | Push -1 if S1<S0, 0 if equal, 1 if S1>S0 |
< | Push the contents of memory cell S0 |
> | Store S1 into memory cell S0 |
^ | Push a copy of S<S0+1> - so 0^ duplicates the top |
v | Remove S<S0+1> and push it on top - so 1v swaps the top two |
d | Drop S0 |
g | Add S0 to the program counter (relative jump) |
? | Add S0 to the program counter if S1 is zero |
c | Push the program counter to the call stack, jump to S0 |
$ | Pop the call stack into the program counter (return) |
p | Print S0 as an integer |
P | Print S0 as an ASCII character, using the low 7 bits |
! | Terminate |
Two things follow immediately from that table, and they shape everything about writing the language.
There are no literals above 9. To print a capital H, ASCII 72, you must build 72 from single digits and arithmetic - 89* gives 72 in two characters, and the official Hello World opens with exactly that. Every string constant in a HackVM program is really a sequence of small arithmetic puzzles, and shorter programs win by finding better factorisations. This is the language’s defining activity.
Jumps are relative and computed. g and ? add a stack value to the program counter, so a loop is written by pushing a negative offset, and every edit to the body of a loop changes the offset that closes it. There are no labels. Subroutine calls via c take an absolute target, again computed on the stack. In practice this means a HackVM program of any length is written, and then rewritten, around its own character offsets.
Two Official Examples
From the documentation itself:
| |
Pushes 7, pushes 8, multiplies, prints the integer: output 56.
| |
Output 945321. The program uses ^ to copy a value from deep in the stack, v to rotate one to the top, : to compare, and ? to jump conditionally - roughly the whole machine exercised in twenty-four characters. Programs at this density are normal for the language rather than exceptional, because challenge answers are scored by producing the right output at all, and terse programs are easier to reason about than long ones when every jump is a hand-counted offset.
For a sense of what real work looks like, Progopedia’s documented Fibonacci program keeps its loop counter in memory cell 0, the ASCII codes for comma and space in cells 1 and 2, and the two most recent Fibonacci values in cells 3 and 4, driving the whole loop with a single ? and a single g. Its factorial program is structured the same way and is reported to terminate with an integer overflow exception at 13! - a value that exceeds the documented 32-bit range but not the 64-bit bound the Python interpreter actually enforces, so where the ceiling falls depends on which interpreter you run. Either way the machine’s limits are close enough to the surface that ordinary programs hit them.
Implementations
There are two implementations by the author and very few by anyone else.
The Python interpreter (hackvm.py) is the reference. It is a single file, and reading it is the fastest way to resolve ambiguities in the prose specification. Several details are visible only there: execution is capped at 10,000 cycles and aborts with a too many cycles exception beyond that, so unbounded search programs cannot simply be left to run; memory reads and writes outside 0-16383 raise access-violation exceptions rather than wrapping; ^ and v bounds-check against the current stack depth; and errors are reported on standard error in a fixed !ERROR: format that includes the failing instruction, the program counter and the stack depth. It also accepts --trace, which dumps the operand stack after every instruction - the only debugger the language has - and --init, which loads a comma-separated memory image, the mechanism challenges use to supply input.
The published script is Python 2 code: it uses print as a statement and the built-in cmp, both removed in Python 3. Running it today therefore requires a Python 2 interpreter or a small port. Division is Python 2 integer division on integer operands, so / truncates toward negative infinity rather than toward zero - a difference that matters for programs doing arithmetic on negative values.
The JavaScript interpreter is embedded in the documentation page, with fields for the program, for initial memory contents, and a trace option. Progopedia notes that it is stated to differ from the Python one, which is a warning worth taking seriously: on a puzzle site, a program that behaves differently in the two interpreters is a solver’s problem, not the site’s.
Third-party, a C implementation was published on GitHub in December 2013. Rewriting the VM in a compiled language is a plausible response to challenges that require searching a large space of candidate programs, though no published benchmark compares the C, Python and JavaScript interpreters.
Why It Is Dormant
Nothing about the language was ever deprecated or replaced; it simply reached its final form early and had no reason to change. The specification is one page and complete. The instruction set is the minimum needed for a Turing-equivalent machine plus two output instructions. There is no committee, no versioning, no roadmap, and no user base separate from the challenge site.
The signals of dormancy are all secondary: the Esolang article has been a one-line stub since 2015, the reference interpreter has not been ported off a Python version that reached end of life on 1 January 2020, and the documentation page carries no dates at all. hacker.org itself remains online and its challenge system still runs, so HackVM is better described as finished and static than as abandoned.
Why It Matters
HackVM is a useful specimen for two reasons.
First, it is an unusually clean example of a language as a puzzle substrate. The design decisions that look perverse in a general-purpose language - no literals above 9, relative jumps only, no input instruction, a hard cycle cap - are all correct for the actual requirement, which is that a challenge author must be able to state a problem precisely and verify an answer mechanically. Constraints that would be defects elsewhere are the product here.
Second, it shows what minimal Turing-equivalent design costs its users. The machine has a stack, addressable memory, arithmetic, comparison, conditional relative jumps and a call stack, which is genuinely everything needed to compute. What it lacks is every affordance that makes computation writable by humans: names, labels, structure, literals, types, and any error reporting richer than a program counter. Working in it teaches, faster than an argument could, that the difference between a language you can compute in and a language you can program in is almost entirely made of affordances rather than power.
For anyone who wants the same lesson with a curriculum attached, the nand2tetris Hack VM is the better-documented place to get it. For anyone who wants the lesson delivered as a puzzle with a scoreboard, this is the one that was built for it.
Timeline
Notable Uses & Legacy
hacker.org programming challenges
The only purpose the language was built for, and stated as such in its own documentation: some of the challenges on hacker.org require the solver to write a Hack VM program that produces a particular result. Progopedia singles this out as the reason Hack VM is unusual among esoteric languages, describing it as one of the few that is really used
Hand-golfed constant generation
Because digits 0 through 9 are the only literals, every other number has to be built arithmetically, so a Hack VM program that prints text is really an exercise in finding short expressions for ASCII codes. The official documentation's own example, 78*p, multiplies 7 by 8 and prints 56; Progopedia's Hello World writeup builds each character code in turn and, for the doubled letters in Hello, triples one computed value on the stack rather than recomputing it. This constant-golfing is the characteristic activity of writing the language
Third-party reimplementations
A small number of independent interpreters exist, notably a C implementation published on GitHub in December 2013. There is no ecosystem beyond this - no package manager, no compiler targeting the VM, no standard library
Esoteric-language catalogues
Hack VM has entries in the Esolang wiki and in Progopedia, which is how the language appears in encyclopedic language lists at all. The Esolang entry is a one-line stub; the Progopedia entry, which documents the full instruction set along with Hello World, Fibonacci and factorial programs, is the most complete secondary description in existence