53 Failed Compilers, 1 Burned-Out Dev: Why I Still Build VMs

Aryan ChauhanAryan Chauhan
17 min read

Introduction: The Pretense to Become Cool

I like to call myself a programmer — not quite a software developer yet. Why? Because I haven’t actually developed software... at least, not anything that someone would willingly use (without a little bribery or blackmail). Sure, there's something cooking in the background, but it's still half-baked. So for now, let's stick with "programmer."

The first language I ever tried to learn was C++. Yep, I went full throttle into the deep end — classes, objects, inheritance, function polymorphism — the whole buffet. In hindsight, that might not have been the brightest idea. Things were going okay-ish until I hit inheritance. That’s where my brain threw in the towel. No matter how many times I reread the chapter or watched videos, it just wouldn’t click.

Frustrated and mildly traumatized, I stepped back. I asked myself, “Why did I want to be a programmer in the first place?” The answer was simple: programming is just so cool. It’s powerful, limitless, like wielding a digital magic wand. But here I was — demotivated, confused, and kind of annoyed at myself.

So, I made a pivot. Enter: Python.

Whether it was the book I picked up (Python Crash Course) or just the programming gods finally taking pity on me, something clicked. Everything made sense. It felt like someone turned the lights on in a previously haunted mansion.

The feeling! I still remember it — that moment when I could finally write real programs. Simple, sure. CLI-based, definitely. But they worked. And that was magic.

Now, most people would take this as a win and double down on mastering that one language. But me? I still felt like something was missing...

Curiosity is my biggest asset — and my most relentless tormentor.

I knew Python was an interpreted language. I had a rough idea of what that meant (thanks to vague explanations and half-baked blog posts), but no clue how it actually worked under the hood. Sure, Python’s high-level abstraction is sweet — like sugar on top of an already delicious cake — but I wanted to know how that cake was baked. What were the ingredients? Who was the chef? What was happening behind the scenes?

Most people — wiser people — advised me to stay away from the murky depths of low-level programming. “It’s too complicated,” they said. “Unnecessary pain,” they warned. But the questions wouldn’t stop piling up, and eventually, I had no choice but to dive in.

And this... this is where it all truly began.

Buckle up — you're in for a ride. A long, twisty, often confusing, but incredibly rewarding journey. You can tell me later if it was boring, a waste of time, or the best thing you've read in a while. But for me, at the end of it all, I was satisfied. Not because I had all the answers — far from it — but because I finally knew what questions to ask.

There are still many mysteries I want to unravel — questions that may only be answered by building an operating system from scratch. Am I ready for that? Nope. Not even close. But one day, I will be. (And no, this won't be your next Windows or Linux distro. It’s purely for the thrill of learning.)

So...
Let’s begin the journey!

After I finished learning Python, I still remember the first complicated program I ever wrote: a simple little To-Do list app. You could store your tasks in a file and reuse them later! Nothing fancy — but hey, it worked, and it felt elegant. That sense of creating something that actually did something? Pure bliss.

While riding that high, I stumbled across something in VS Code that blew my mind: you could look at the source code of the headers you imported. What?! How had no one told me this before?

Naturally, I went wild.

I started digging through the imported modules like a mad archaeologist. One file led to another. Layers upon layers of abstraction, classes, inheritance, and cryptic code constructs. It was like opening a treasure chest only to find a Rubik’s Cube... on fire... written in a language you barely understood.

😵 I didn’t get a thing. Not even close.

That broke me a little. In hindsight, I was clearly setting myself up — who in their right mind thinks they can casually understand the inner workings of the Python standard library after just finishing a crash course?

Crushed and confused, I did what any sensible person would do in such a moment of crisis:
I binged Naruto and finished the first part.

After that spiritual refreshment, I had a new perspective. Instead of trying to scale Mt. Everest in flip-flops, I figured I'd better understand how the language worked first. I knew Python was written in C, so I circled back to C++ — this time, determined to finish the basics properly.

It took a little time, but this time? I got it. I actually understood it. Like, really understood it.

I also realized I’d been living under a rock when it came to tools like Git and GitHub. So I took a detour to learn those too — and wow, what a game-changer! I still remember the very first thing I searched after creating my GitHub account:
“Python”.

That’s when I discovered the interpreter behind it all is called CPython.

Naturally, I cloned the whole repository to my machine.
And let me tell you — that was simultaneously one of the best and worst decisions I’ve ever made.

Overwhelmed by the Beast

Unlike my naive (read: dumb) attempt at understanding the Python standard library, this time I knew I was out of my league. But just for the thrill of it — for the sheer joy of staring into the abyss — I decided to peek into the source code of CPython.

No joke — I got a headache.

It was that complex. It felt like trying to learn English as an alien who just crash-landed on Earth. Beautifully structured folders, an endless sea of .c and .h files, cryptic macros, deep logic layers... it was art. Headache-inducing, soul-crushing art.

And yet — I was in awe.

It was a magnificent beast. A beautiful, terrifying, majestic monster of a codebase. And no, don’t worry — I don’t have a fetish for code. I just genuinely enjoy complexity, in all its forms. Code, chess, math, politics — if it’s messy and layered and borderline impossible to grasp, I probably love it.

Strangely enough, this whole experience didn’t discourage me. It fueled me.

I realized something important that day: with enough time and experience, I will be able to understand CPython. Heck, maybe even contribute to it. Or build something of that scale myself. The idea gave me chills.

Naturally, I kept going. I checked out the Linux kernel. I wandered into random GitHub projects with thousands of stars. I stared at complex build systems, container runtimes, game engines, anything I could find. I think I spent about 7 straight hours that day just admiring massive codebases — not reading them, not understanding them, just soaking them in.

Because one day, I want to build something that makes someone else feel the same way.

Inspired. Overwhelmed. And hopelessly curious.

The Grandfather

It was finally time to descend into the basement — to meet the legend, the OG, the one true ancestor of them all: C.

(Spoiler: I'm so used to it now that I can't leave it, even if I wanted to.)

C — the grandfather of modern programming languages — is powerful, elegant, and terrifying. Everyone always talked about how “scary” it was, and I never quite understood why. What’s so scary about a simple, minimal language?

Then came my first Segmentation Fault.

Ah. Now I get it.

Apparently, that phrase alone sends chills down spines. Sure, it opens doors to potential security issues, crashes, and undefined chaos — but really? That’s what scared everyone off?

Okay, fine. I get it. It’s hard to give up the cozy comforts of high-level languages: no manual memory management, standard libraries with all the data structures you’ll ever need, garbage collection whispering sweet nothings into your stack.

If it weren’t for my relentless curiosity, I would’ve happily stuck with Python and never looked back.

But alas!

As I used C more and more, I began to understand why high-level languages exist — and why they’re so loved. C++ is like C’s overachieving grandkid who’s trying to give you power and comfort. A higher-level experience with the same low-level punch.

And someone once said it best:

“C is like a grenade — you will surely blow yourself up.
C++ is like a shotgun with a safety — harder to misuse, but one mistake and you’ll blow your entire leg off.”

It’s poetic. It's also painfully accurate.

Just like Uncle Ben said in Spider-Man:

“With great power comes great responsibility.”

To which C adds:

“...and the headache of having to understand and consider every damn platform out there.”

Thanks, Grandpa.

The New Pile of Questions... No!!!!!

The best thing about diving into C? I finally got a taste of what real power felt like. Not just in a “look what I can do” kind of way — but in the “I now understand what high-level languages have been shielding me from” kind of way.

With this newfound clarity, I began to theorize how interpreted languages like Python actually worked behind the scenes. I no longer viewed Python as this magical, untouchable entity. Instead, I started seeing it as... a wrapper. A cozy, friendly, high-level wrapper built atop hardcore low-level logic.

My theory?
Python represents everything as a kind of C++ object — more accurately, a tagged enum of sorts. That’s why lists can hold any type of element. That’s why I never had to deal with memory management or even think about platform differences — Python just handled it all. Blissful abstraction at its finest.

It finally clicked: This is what high-level languages are all about.
This is the sweet, sugar-coated, platform-agnostic heaven they’ve been trying to sell me all along.

And for the first time, I could reverse-engineer language behavior in my head. I could look at what a feature did and take a pretty solid guess at how it might be implemented under the hood.

That felt like a real milestone.

So, naturally, I rewarded myself in the only way that made sense:
I binged Naruto Shippuden like a madman.

About to Go Crazier

After that Naruto-fueled refreshment break, I was ready to delve even deeper into the abyss of computing. This time, my curiosity pointed toward one of the biggest black boxes in programming: compilers.

What even is a compiler? How does it work?
Little did I know — this rabbit hole was way deeper than I expected.
And oh boy, was I about to fall hard.

At that point, I was dangerously close to a turning point in my journey. I just didn’t realize it yet. It would still take a few months to fully snap — but the process had already begun.


As I started researching compilers, I stumbled upon resource after resource, each more fascinating than the last. It became obvious: compilers are marvels. Literal works of art. I dug deeper. Then deeper. And eventually got so deep... I forgot how to climb back out.

The general steps that most compilers follow started making sense:

  • Reading your source code

  • Lexing it into tokens

  • Parsing those tokens into an AST

  • Analyzing that AST

  • And finally... generating code

(Note: This is just a simplified list. Every compiler has its own special sauce and extra steps.)

Now, while the first few steps were intellectually stimulating, it was the final step — code generation — that truly hooked me. That’s where I stumbled upon something that completely flipped my brain:

Wait… there’s a thing called assembly language?

And that, my friends, was just the scratch on the surface.


I soon discovered:

  • There are many CPU architectures. Hmm.

  • Each CPU has its own Instruction Set Architecture (ISA). Hmmm.

  • Some CPUs use big endian, others use little endian. Hmmmmmm.

  • Programs can be cache-friendly or not, which affects performance. Hmmmmmmmm.

  • There’s this mystical thing called virtual memory. Hmmmmmmmmmmmmmm.

  • Oh, and the difference between the stack and the heap? Hmmmmmmmmmmmmmmmmmm.

  • Programs talk to the OS via system calls? Hmmmmmmmmmmmmmmmmmmmmmmmmmmm.

Each discovery pulled me deeper into the dungeon of computing.

And I had yet to reach the real depths.

The Foundation

I was so intrigued by assembly and machine code that I had to know how a CPU actually worked. It wasn’t optional — it was a compulsion. And looking back, it was one of the best decisions I ever made.

I went to the foundation — right down to the bedrock. I studied basic semiconductor technology, the physics behind it, and the various forms of transistors.

Then came lithography — the intricate process of printing circuitry onto silicon wafers. I didn’t go too deep, just enough to satisfy my curiosity without opening another bottomless pit. Questions did pop up, but not strong enough to force a deep dive. So I floated on the surface and kept moving forward.

Once I had a basic grasp of semiconductors and lithography, it was time to dive into something I loved — the world of logic building using logic gates.


I’d like to give a huge shoutout to Crash Course for their amazing Computer Science playlist. That series was one of the best free resources I found during this part of the journey. Thanks to it, I gained a newfound appreciation for the technology we all take for granted — sitting casually in our pockets and on our desks.

I won’t spoil too much for anyone interested, but I’ll just say this:
The modern world grossly underestimates the power it holds in the palm of its hand.


The Limits of Progress

As I learned more, a sobering truth became clear:

The only thing truly limiting humanity from achieving more computing power using existing technology… are the laws of nature.

Sure, manufacturers have pulled off clever workarounds and mind-bending engineering feats to push the limits of Moore’s Law — but they are just that: workarounds.

Yes, quantum computing holds immense promise — but the way it’s marketed makes it sound like we’ll have God whispering algorithms into our ears.

Let’s be honest:
Quantum computing will be revolutionary, but not in the way most people imagine. It won’t replace classical computing for everything, and it’s not some magical universal fix.

If you ask me, the real breakthrough we need is something else entirely:

Room-temperature superconductors.

Now that would change the game completely.
It would unleash a wave of innovation and performance we can’t even fully imagine yet.

Until then, we keep innovating within the walls of physics — climbing higher with every clever workaround.

The Newfound Motivation

As I completed my journey to the depths of computing—just to glimpse the bedrock—I emerged a wiser man. I had gained a newfound appreciation for the digital world and the raw potential I held in my hands. I felt strong.

I discovered software like Logisim, which allowed me to build my own CPU architecture! I worked on it for a few days, until I stumbled upon the concept of a Virtual Machine.

A virtual machine, in essence, is a machine that doesn't exist in the physical world—you can't see it, touch it, hear it, or feel it. Yet, it exists virtually in the form of machine code. Here, I use "machine code" to mean the language directly understood by a computing device, whether it uses binary, ternary, or any other number system.

More loosely, a virtual machine is a piece of software that mimics a real or imaginary physical machine. Let's explore this with a fun example:

Imagine you have a desktop computer, but you wish you had an iPhone so you could show off your mirror selfies. Unfortunately, you're broke. Now, suppose I'm an engineer at Apple who knows the ins and outs of any Apple device. Theoretically, I could write a program that perfectly mimics an iPhone X. You download this program and run it on your desktop to get a taste of the iPhone experience. Sure, the camera will only be as good as your desktop’s camera—but hey, that’s a pretty convincing simulation of the iPhone brand! Of course, you can’t hold your monitor in front of a mirror, but you get the idea.

That, essentially, is what a virtual machine is. In theory, I could mimic any machine in existence using just code! Though, perfectly mimicking hardware behavior is always impossible. Why? Using the iPhone example—your hardware limits the fidelity. A motion sensor can't be accurately simulated unless both the signals and their interactions are simulated, and even then, it would still be virtual. It won’t actually detect your motion.

This technology fascinated me—especially as I was already in love with computing. I wanted to explore every kind of machine that could exist, whether practical or purely theoretical. Now, I had the means to create my own CPU architectures! As many as I wanted, however I wanted. The possibilities felt endless.

But then... things took a turn.

The Pit of Compiler Development

Before building my virtual machine, I made a stop at the station everyone called "Compiler Development." Sure, virtual machines were exciting—but compilers were just as fascinating. I understood the theoretical steps a compiler took, but I wanted to walk that path myself.

I chose C++ as my starting point. I scoured the internet, looking for a guide to help me begin. With patience, I built a lexer, a basic parser, and a simple error-handling mechanism. I named this project ZoroC (short for Zoro Compiler).

Note: I’m a One Piece fan.

Things went smoothly until I tried to make the parser slightly more complex. At the time, I had no real programming experience. I had some basic problem-solving skills, but that was it. I’d been diving so deeply into the theoretical side of computing that I’d stopped writing code altogether.

When I hit a wall with ZoroC and couldn’t figure out how to fix it, I abandoned the project. Then came ZoroC V2... which also failed. After that: Cyclops (3 attempts), Dynamite (4–6 attempts), Sigar, Crystal (around 8 attempts)... 53 attempts in total—and none of them worked.

I switched from C++ to Python, hoping it would be easier. It wasn’t. I never understood why I was failing. But looking back now, maybe if I had tackled smaller projects first to gain experience, things might have been different.

I did gain experience—but only in failure. When the final project, Retry, failed... I truly felt defeated.

While working on these, I discovered LLVM, which fascinated me deeply. Still, I never made it far enough to transpile my language to LLVM IR. Even if I had, I might not have done it—my goal was educational, and building everything myself felt more meaningful.

But I never made it that far.

After giving up on compilers, I remembered that Python is an interpreted language. So I thought—why not build something similar? I began again.

My first interpreter attempt was called Astro. I was so focused on building the compiler side of things that I didn’t realize I was just repeating the same mistakes.

On my next attempt, I focused on just getting addition to work. And I succeeded! I managed to parse complex expressions using infix-to-postfix conversion. I had a working expression evaluator at last!

But again... it all fell apart when I tried to make things more complex.

After 16 more attempts, I finally created a project that supported variable declarations, complex expressions with variables, and basic condition evaluation. It was the culmination of everything I’d learned from all my failures.

But it didn’t last.

When I got to if statements, I hit another wall.

You might wonder—why did I always start over from scratch every time something failed?

The answer is simple: the code would reach a point of no return. It would become so tangled that even I, its creator, couldn’t make sense of it anymore. It was like an alien trying to understand English.

My biggest mistake? Not using Git.

Had I used version control, I could’ve reverted to a working state. But I never did. Despite knowing about Git, I never gave it a try.

I switched to a Linux-based distro just a month after buying my laptop. I wanted to free my machine from the burden of Windows and go lightweight. But I regret that decision. Now, every time I want to test something, I can only verify it on Linux—not Windows. The Windows API is a mess. Linux treats everything as a file, which simplifies things. But Windows has its own approach, and that makes cross-platform testing frustrating. Another dumb mistake.

After switching to Ubuntu, I went distro-hopping until I finally settled on Fedora. Why is that relevant? Because on Windows, something kept eating up my disk space every day. I never figured out what, but now I suspect it was temporary files piling up. In just a few weeks, 1 GB would vanish without explanation.

But Linux wasn’t always kind either.

During what I now call my final attempt (the one after the project called Drek, which failed during if statements), a large partition of my drive got corrupted for no reason. That partition had the project that finally supported both if and else-if. And I lost it all.

I was so frustrated, I took a month-long break... and binge-watched Game of Thrones.

I truly gave up on writing a programming language at that point—and shifted my focus to Virtual Machines.

That’s another story entirely... and it deserves its own chapter.

0
Subscribe to my newsletter

Read articles from Aryan Chauhan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Aryan Chauhan
Aryan Chauhan