The Anatomy of a Virtual Machine: Lessons from a Self-Taught Engineer

The VM Journey

The Random Attempts

My very first virtual machine was an 8-bit stack-based VM with only a handful of instructions in its ISA. It had no name — it was simply an experiment to understand how a virtual machine actually worked.

At the time, I wasn’t even using Git or GitHub properly.

Each new attempt improved on the last. Slowly but steadily, I pushed myself to experiment, debug, and solve every problem without relying on external help. This not only improved my problem-solving skills but also sharpened my ability to imagine the countless edge cases a VM might encounter.

Soon, it became a personal challenge:
How much better could the next version be?

The answer was always: "A lot."

My first attempt was a single-file program with maybe 500 lines of C++ code.

Yes — I was writing everything in C++.
Fun fact: I was so deep into VM development that I didn’t even bother to learn any new C++ features after C++17.

The second attempt was a noticeable improvement, and each successive iteration taught me something new. By my fourth serious attempt, I wrote something I named Fraud — a simple stack-based virtual machine that supported over 40 operations.

It had:

  • A working stack
  • Basic control flow
  • A simple but flexible architecture

Those early VMs weren’t capable products. They had no security features and couldn’t handle the increasing complexity of real programs. They were rough, experimental playgrounds — designed only to test ideas and see what clicked.

Fraud was the first truly complex VM I built.

If memory serves, it was somewhere between 2000 to 3000 lines of code. Its architecture was extremely simple:

  • A reader parsed a lightweight binary file
  • Instructions were loaded into a memory buffer accessible by 64-bit words
  • The core started and began execution

That was it.
No central manager.
No multithreading.
Just one core, one job. Plain and simple.

And to my surprise — it worked like a charm.

That success pushed me further. Stack-based virtual machines are simple and powerful, but they still didn’t give me what I was really chasing — something closer to a real CPU architecture.

That’s where my real VM journey began.

The Enigma Series

Starting from the fifth attempt came the Enigma VM — the first in a series of Enigma VMs. This time, Git and GitHub were properly utilized. Enigma implemented a general-purpose core with a custom architecture. It was a hybrid, using both a stack and registers.

The memory design wasn’t the smartest — there were separate memory regions for byte-sized, word-sized, dword-sized, and qword-sized data. Addressing was done using metadata embedded within the address itself.

This decision stemmed from the realization that byte manipulation was time-consuming, and when every millisecond counts, optimizing even that makes a difference.

The problem was simple, but the solution — to me — wasn’t even worth trying. Yet I did it anyway, because I was still learning.

The next VM in the series, Enigma VM Buffed, was an experiment to test a new memory model using a single addressable byte array — much closer to real-world memory.

Enigma VM Buffed exceeded my expectations. My standard benchmark was a 1-billion loop test. The results? Around 40–47 seconds — not bad at all. I was happy that it beat Python, but still not satisfied. So I moved on to Enigma VM V2.

Enigma VM V2 introduced a hierarchical structure for the first time. Although it was inefficient, it was a solid beginning. The architecture included:

  • A Manager
  • An Overseer
  • A Core

I don’t know why I thought one overseer per core was going to be efficient, but I did it. The manager oversaw the entire VM and communicated only with the overseer. Its job was to parse the now-binary input file, prepare the memory, and boot up a core.

This is the structure I’ve refined ever since.

After the success of Enigma VM V2, I shifted focus toward speed. I realized that C++ was too heavy for my use case. So much of the control flow was hidden, and I couldn’t tell what was going on inside the standard library.

While the STL is optimized for a wide range of situations, it was over-optimized for my VM’s narrow needs. That overhead was dragging me down.

All signs pointed to one thing: C.

It gave me near-total control, but the tradeoff? I had to write all data structures from scratch. Hellish? Yes. But also incredibly educational.

Khoya VM

The first VM I made in C was Khoya. In my mother tongue, Khoya is what remains after stripping the kernels off a maize cob. It also indirectly translates to “F***.”

With that amazing name, I dove in.

First, I implemented every data structure I thought I might need — dynamic arrays, linked lists, queues, stacks, hash maps. I didn’t end up using them all, but it was fun, and I kept going.

Within a few weeks, Khoya was already running its first program. The architecture was much more versatile and flexible. It was easy to expand and modify. The structure was:

  • Manager
  • Cores

The manager supervised the entire VM and all cores. A lot more security features were in place. Managers tracked memory, system calls, and more. Cores had to request privileged operations from the manager.

Aspire VM

The next step was Aspire VM — the culmination of everything so far. It was the fastest, most efficient, and most complex VM I had made. It completed the 1-billion loop test in just 10–12 seconds — and after a few optimizations, I brought it down to 6–8 seconds.

Its architecture closely resembled that of the current flagship:

Merry

Merry is my most advanced virtual machine yet. It will receive its own detailed documentation, as its structure deserves a thorough breakdown.

Comments