Your Computer Never Added 10 + 20
Your compiler quietly did the math before your program even started. Here’s how, and why we’re going to build our own.
Let me show you something suspicious.
int main()
{
int x = 10;
int y = 20;
return x + y;
}You’ve written code like this a thousand times. Compile it, run it, and the program returns 30.
Now here’s a bet. Compile that with optimizations on, open the assembly, and go looking for the addition. The add instruction. The moment the machine takes 10 and 20 and combines them.
You might not find it.
The program still returns 30, but the CPU may never actually add 10 and 20 while it runs. Something worked out the answer earlier and put it straight into the generated code.
That something was the compiler. It did the work early and didn’t tell me.
What else is in there that I don’t know about?
So that’s what this series is. We’re going to follow your code from the text you typed to the thing that runs, and then build our own small programming language from scratch: a lexer, a parser, an interpreter, and eventually a virtual machine.
We start with your CPU, and I have bad news: it has absolutely no idea what your code means.
What a CPU actually understands
Think of the CPU as an intern. A very fast intern, billions of tasks per second, but a very literal one. It doesn’t know what a variable is. It doesn’t know what int means. It only understands a small menu of orders:
- put this number over here
- add these two numbers
- jump to that instruction
And it doesn’t get those orders as words. They arrive as numbers. On an x86 chip, “put the number 10 into a slot called eax” looks like this, in hex:
B8 0A 00 00 00You don’t need to understand those bytes yet. The important thing is that the CPU doesn’t understand C++ directly. It understands much simpler, machine-level instructions.
Now picture yourself writing int x = 10; and handing it to that intern like a sticky note that says “store ten in x.” It understands nothing on that note.
So there’s a gap. You think in ideas, and the CPU follows numbered orders. Something has to sit in the middle, read your ideas, and turn them into orders the chip can run.
That something is just another program.
The translator
hardware involved, and nothing a person couldn’t explain on a whiteboard.
That raises a chicken-and-egg question. If compilers are programs, what compiled the first compiler?
The answer is bootstrapping. Early compilers were built using simpler tools, and those compilers were then used to build more capable ones. Each generation helped build the next.
You write a tiny, crude compiler by hand. Use it to compile a slightly better one. Use that to compile a better one still. Each generation lifts the next, like a ladder that builds itself. It compiles all the way down, with a hand-built rung at the bottom.
So the whole journey of your three lines looks like this:
Five boxes. We’re going to understand every one of them. One of them we’re going to build.
Two ways to run code
A compiler isn’t the only way a programming language can be executed. Some languages use interpreters, virtual machines, or a mixture of techniques.
- The compiler way: Hire a translator to convert the whole book upfront. You wait a bit, then read the finished English version at full speed, as many times as you like.
- The interpreter way: Hire someone to sit next to you and translate each sentence as you read it. You start immediately, but they translate again every single time.
Neither one is wrong. It’s a trade between paying the translation cost once up front and paying a little each time the program runs.
Myth-busting time
You’ve heard this one: “C++ is compiled, Python is interpreted.” It’s a nice sentence. It’s also, well, a bit of a lie.
- Python compiles your code to bytecode first, then a virtual machine runs it. Java does something similar and then JIT-compiles the busiest parts while the program is running. JavaScript engines like Chrome’s V8 watch which code gets used most and speed those parts up on the fly. Most real languages sit somewhere in between.
- Bytecode and JIT might sound intimidating right now. We’ll build a bytecode machine ourselves near the end of the series, and it gets a lot less scary once you’ve made one.
So how did it know?
The compiler isn’t guessing. It has enough information to prove that the result must be 30.
Get Abdul Ahad’s stories in your inbox
Join Medium for free to get updates from this writer.
Because it had to read your code closely enough to reason about it. In this case, that went something like:
1. int x = 10; creates a variable holding 10.
2. Nothing changes x before it’s used.
3. Same story for y.
4. So x + y can only be 30. Always.
That trick is called constant folding, and we’ll build it ourselves in a later article.
The part I find interesting is what it implies: a compiler can only do something clever with your code if it has first turned your raw text into something with structure. That first step is where our building begins.
Your turn (five minutes, no installs)
Go to godbolt.org (Compiler Explorer, free) and paste this in:
int main()
{
int x = 10;
int y = 20;
cout << x + y;
}Pick a C++ compiler, then switch the optimization flag between -O0 (off) and -O2 (on) and watch the assembly change.
With it off, you’ll see the variables being stored and an add. With it on, the add should disappear and you’ll see something close to mov eax 30; . Exact output varies by compiler and version, so yours might look a little different.
Now for the real bet. Change the program so that x is read from the user instead:
int main()
{
int x;
cin >> x;
int y = 20;
return x + y;
}Recompile with -O2. Will the compiler still fold x + y into a constant?
Make your guess before you scroll down. (Answer is at the bottom of the article. No peeking.)
What this series is
So far, we’ve been looking at what existing compilers do. Now we’re going to build a tiny one ourselves.
Over this series, we’ll create a small programming language called Lumen.
Right now that’s just text, and Lumen doesn’t exist. By the end, we’ll have taught a computer to read it, understand it, and run it.
Here’s the plan:
It’s twelve articles, all in C++. Each one builds one piece and ends with something you can run.
Up next: reading code character by character
Before Lumen can understand what let x = 10; means, it first has to read those characters and figure out what each piece represents.
- How do you get from that pile to something a program can reason about?
- In Article 2, we write the first real code of Lumen: a lexer in C++. By the end, you’ll feed it let x = 10 + 20; and watch it come back as a clean list of tokens.
- If you want to follow along, the next article is where we actually start building Lumen.
- And I’d love to know: what’s something about how code runs that you’ve always wondered about but never looked up?
- I’m Ahad. I’m writing this series as I build Lumen.
The answer to the bet
No. Once x comes from the user, the compiler can't fold x + y into a constant anymore.
It doesn’t know what the user will type until the program actually runs, so there’s no value to bake in ahead of time. It can still optimize other things, but that particular trick is off the table.
That’s the whole idea behind constant folding in one sentence:
If the compiler can prove the answer in advance, it does the math early. If it can’t, the math has to wait for runtime.









