V8 Engine · Lesson 1

Watch V8 Compile Your JavaScript

Why this lesson: Your mission is to build a deep mental model of how V8 executes JavaScript. Before we build V8 from source, here's the shortcut: you already have V8 on your machine — it lives inside Node.js. In 10 minutes you'll make it show you the exact instructions it runs.

JavaScript looks interpreted — you hand V8 text, things happen. But V8 doesn't run your text. It compiles it, in stages, and the first stage produces a compact instruction format called bytecode. Today's single win: read the bytecode V8 generates for a function you wrote, and understand why it exists.

The pipeline, in one picture

Every JavaScript function travels down this assembly line inside V8. Today we stop at the second station — Ignition, the bytecode interpreter.

Source
your JS text
→
Parser
→ AST
→
Ignition
bytecode + runs it
→
Sparkplug
Maglev · TurboFan
machine code (if hot)

Ignition is a register machine that interprets bytecode. Most code only ever runs as bytecode — it's small and fast to produce. Only "hot" functions (run many times) get promoted to the optimizing compilers on the right, which emit real machine code. We'll meet those in later lessons. Source: Firing up the Ignition interpreter, Launching Ignition and TurboFan.

Key idea — the accumulator. Ignition bytecode is built around one special register called the accumulator. Most instructions implicitly read from and write to it, which keeps the bytecode tiny. Watch for it below: Ldar = "Load Accumulator from Register", and Add adds to whatever's already in the accumulator.

Do it: make V8 print bytecode

1Create a tiny script. In a terminal:

cat > /tmp/v8demo.js <<'EOF'
function add(a, b) { return a + b; }
add(1, 2);
EOF

2Ask V8 to show its work. The --print-bytecode flag is a real V8 flag; Node passes it straight through to the embedded engine. We filter to just our function:

node --print-bytecode --print-bytecode-filter=add /tmp/v8demo.js

You'll see something like this (addresses will differ — they're live memory pointers):

[generated bytecode for function: add (<SharedFunctionInfo add>)]
Bytecode length: 6
Parameter count 3
Register count 0
Frame size 0
   21 S> @    0 : 0b 04        Ldar a1
   30 E> @    2 : 3f 03 00     Add a0, [0]
   34 S> @    5 : b3           Return

Read it, line by line

Three instructions. That's the entirety of a + b. Here's what each does:

BytecodeWhat it means
Ldar a1Load Accumulator from Register a1. a1 is the second argument, b. The accumulator now holds b.
Add a0, [0]Add register a0 (the first argument, a) to the accumulator. The accumulator now holds a + b. The [0] is a feedback slot — index 0 in this function's feedback vector, where V8 records the types it saw (two small integers). That recorded feedback is exactly what the optimizing compilers read later to generate fast machine code.
ReturnReturn whatever is in the accumulator.
Why "Parameter count 3" when add takes 2 args? The hidden first parameter is the receiver — the value of this. Every JS function call carries it, so V8 counts it as a parameter. The 21 S> / 30 E> columns are source-position markers (Statement / Expression) that map bytecode back to your source for debugging and stack traces. Walkthrough: Understanding V8's Bytecode (F. Hinkelmann).

Bonus: catch V8 optimizing the function

The whole point of recording type feedback is to promote hot functions to machine code. You can force and observe it with --allow-natives-syntax, which unlocks %-prefixed intrinsics (V8's internal test hooks — not real JS):

cat > /tmp/v8opt.js <<'EOF'
function add(a, b) { return a + b; }
add(1, 2);
%OptimizeFunctionOnNextCall(add);   // tell V8: compile this now
add(3, 4);              // ...runs as machine code
console.log(%GetOptimizationStatus(add).toString(2));
EOF
node --allow-natives-syntax /tmp/v8opt.js

The number printed is a bitfield. A bit meaning "optimized" being set tells you the function is no longer running as bytecode — TurboFan/Maglev took over. We'll decode this properly when we reach the optimizing tiers.

Check yourself feedback loop

1. In Ignition's design, what is the accumulator?
2. Why does the bytecode for add contain a feedback slot [0]?
3. Match each bytecode to its effect, then check:
Ldar a1
Add a0, [0]
Return
I'm your teacher — ask me anything. Stuck on the output? Curious why Register count 0? Want to see the bytecode for a for loop or an object property access? Just ask in the chat. Try: "Show me the bytecode for a loop and explain the jumps," or "What's a SharedFunctionInfo?"

Where this lands you

You can now make V8 reveal its bytecode and read a simple function's instructions — the ground floor of its execution model. Next we'll map the whole assembly line and see when a function jumps from bytecode to machine code. Then we build V8 from source so you can drive it directly from C++.

Reference docs for this lesson: V8 Inspection Flags cheat sheet · V8 Glossary

V8 Engine learning track · Lesson 1 of an ongoing series · Built around your mission: deep architectural understanding via the embedding API.