Skip to main content
Transform Abstract Syntax Trees into compact bytecode and execute them in a stack-based virtual machine. This is the core execution engine that brings JavaScript programs to life.

Overview

Bytecode is an intermediate representation between high-level source code and machine code. A virtual machine (VM) interprets bytecode instructions to execute programs.
Bytecode execution provides a balance between interpretation speed and implementation complexity, making it ideal for dynamic languages like JavaScript.

Bytecode design and instruction set

A well-designed instruction set is crucial for efficient VM execution. Each instruction should be simple, compact, and map directly to common operations.

Instruction format

Bytecode instructions typically use a fixed or variable-length format:

Instruction categories

Instructions for managing the operand stack:
  • PUSH_CONSTANT: Push a constant value onto the stack
  • POP: Remove the top value from the stack
  • DUP: Duplicate the top stack value
  • SWAP: Swap the top two stack values
Binary and unary operations:
  • ADD, SUB, MUL, DIV, MOD: Arithmetic operations
  • EQ, NEQ, LT, GT, LTE, GTE: Comparison operations
  • AND, OR, NOT: Logical operations
  • NEG: Unary negation
Loading and storing variables:
  • LOAD_VAR, STORE_VAR: Local variable access
  • LOAD_GLOBAL, STORE_GLOBAL: Global variable access
  • LOAD_UPVALUE, STORE_UPVALUE: Closure variable access
Branching and jumping:
  • JUMP: Unconditional jump to offset
  • JUMP_IF_FALSE: Conditional jump (for if statements, loops)
  • JUMP_IF_TRUE: Conditional jump (for logical OR)
  • LOOP: Jump backward (for loop constructs)
Function invocation and returns:
  • CALL: Call function with N arguments
  • RETURN: Return from function with value
  • CLOSURE: Create closure with upvalues

Compiling AST to bytecode

The compiler traverses the AST and emits bytecode instructions:
Jump offsets must be carefully calculated and patched after emitting forward jumps, as the target location isn’t known until the code is generated.

Stack-based VM execution

The virtual machine maintains an operand stack and executes instructions sequentially.

VM architecture

1

Instruction pointer

Tracks the current position in bytecode, advancing after each instruction.
2

Operand stack

Holds intermediate values during computation. Operations pop operands and push results.
3

Call frames

Maintains function call context including return address and local variables.
4

Global environment

Stores global variables and functions accessible from anywhere.

VM implementation

Stack-based VMs are simpler to implement than register-based VMs but may require more instructions for complex operations.

Optimization techniques

Instruction dispatch optimization

Different techniques can speed up instruction dispatch:
Traditional approach using a switch statement. Simple but may not be optimally compiled.

Value representation

Efficient value representation is critical for performance:

Performance considerations

Inline caching

Cache property lookups and method calls for faster repeated access

Constant folding

Evaluate constant expressions at compile time rather than runtime

Dead code elimination

Remove unreachable code to reduce bytecode size

Peephole optimization

Replace instruction sequences with more efficient equivalents
Premature optimization can lead to more complex code without significant benefits. Profile first, then optimize hot paths.

Next steps

Closures and prototypes

Implement closures with environment chains and prototype-based inheritance

Parsing and AST

Learn about lexical analysis, tokenization, and AST construction