Orientation, Prerequisites and Execution Vocabulary
Learning Objectives
Define programming language, program execution, interpreter, compiler, bytecode, runtime, virtual machine, JIT, AOT and REPL — and explain why the categories overlap.
Separate a language specification from its implementations, and source code from intermediate representations, bytecode and machine code.
Give an introductory distinction between static and dynamic typing and between static and dynamic semantics.
Read the code examples, traces and labels used throughout the course.
Prerequisites
Basic programming in any language. No prior compiler or VM knowledge.
Plain-Language Explanation
A program is a recipe written in text. A computer cannot follow text directly — its processor only follows numeric machine instructions. Something must bridge the gap. A compiler translates the whole recipe into another form ahead of time. An interpreter reads a representation of the recipe and carries it out step by step. Many real systems do both: they first translate source code into a compact intermediate form called bytecode, then a virtual machine carries out the bytecode — and may translate the busiest parts into machine code while the program runs (JIT compilation). Around all of this sits the runtime: the services every running program needs, such as memory allocation, garbage collection, error handling and libraries.
Precise Technical Explanation
What is a programming language?
A programming language is a formal notation with a syntax (which texts are well-formed programs) and a semantics (what a well-formed program means — which outputs and effects it may produce). A language is defined by a specification or a reference implementation; it is not the same thing as any one tool that runs it.
What is program execution?
Execution is the process of producing the behaviour a program's semantics prescribes on a real machine: reading input, computing values, producing output and side effects. Ultimately every execution is carried out by machine instructions running on a processor — but there may be several layers (interpreters, VMs, JIT-compiled code) between the source text and those instructions.
What is an interpreter?
An interpreter executes a program by processing a representation of it at run time, rather than first producing a standalone executable. The representation can be the source text itself (rare in practice), an abstract syntax tree (a tree-walk interpreter) or bytecode (a bytecode interpreter, usually the core of a virtual machine).
What is a compiler?
A compiler transforms a program from one representation into another, before or during execution. "Compiler" does not imply "produces machine code": javac emits JVM class files, the C# compiler emits CIL, CPython compiles source to bytecode inside code objects, a TypeScript compiler emits JavaScript.
What is bytecode?
Bytecode is an instruction representation designed for a virtual machine or runtime rather than for a physical CPU. Its instructions are usually higher-level than machine instructions (for example "call this method" or "add the top two stack values"). Examples: JVM bytecode, .NET CIL, CPython's implementation bytecode, WebAssembly, and the MiniLang teaching bytecode in this course.
What is a runtime?
A runtime (runtime system) is the collection of services and machinery needed to execute programs: loading code, allocating objects, garbage collection, exception handling, threads and scheduling, standard libraries, and often a VM and JIT compilers. Its exact composition varies: a C program's runtime is small (startup code and the C library); a Java program's runtime is a whole JVM.
What is a virtual machine?
A virtual machine (in this course: a *process* virtual machine) is an abstract machine defined by an instruction set and an execution model — operand stack or registers, frames, memory model — plus an implementation that executes it. The JVM, the CLR's execution engine and WebAssembly runtimes are virtual machines. (System VMs such as hypervisors emulate whole computers; they are a different topic.)
Source code, intermediate representation, bytecode and machine code people, compilers, interpreters print(base + adjustment); Intermediate representation (IR) AST, SSA graph, LLVM IR a VM or interpreter
GET_GLOBAL 0 / GET_GLOBAL 1 / ADD / PRINT 48 01 f0 (x86-64 add rax, rsi)
Language specification versus language implementation The Java Language Specification and the JVM Specification are documents; HotSpot and OpenJ9 are implementations. Python the language is documented in the Python Language Reference; CPython, PyPy and others implement it. ECMAScript is a specification; V8, SpiderMonkey and JavaScriptCore are engines. Anything not required by the specification — bytecode formats, JIT tiers, object layouts — is an implementation detail that may change.
Static versus dynamic typing (introductory distinction) In a statically typed language, many type errors are detected before the program runs (Java, C#, Rust). In a dynamically typed language, values carry types at run time and type errors are detected when an operation is attempted (Python, JavaScript, MiniLang). Static typing does not mean "compiled" and dynamic typing does not mean "interpreted".
Static versus dynamic semantics (overview) Static semantics are rules checked without running the program: name resolution, scope rules, type checking, "no return outside a function". Dynamic semantics describe what happens during execution: evaluation order, values, errors such as division by zero.
Execution environment and host platform A program runs inside an execution environment (the JVM, a browser tab, Node.js, a WebAssembly runtime) which itself runs on a host platform (operating system plus CPU). The same JavaScript code sees different host APIs in a browser (DOM, fetch) and in Node.js (fs, process).
Interpreter versus REPL A REPL (read–eval–print loop) is an *interface*: it reads one input, evaluates it, prints the result and repeats. Behind it may sit an interpreter, a bytecode compiler plus VM, or even a JIT. The Python REPL compiles each input to bytecode before running it.
Runtime library versus runtime engine The runtime library is code programs call (string functions, collections, I/O); the runtime engine is the machinery that executes programs (interpreter, JIT, garbage collector, loader). System.out.println belongs to the Java class library; the JVM's interpreter and garbage collector belong to the engine.
Where interpreters and runtimes are used Scripting languages, browsers, configuration and query languages (SQL engines interpret query plans), game scripting (Lua), spreadsheets, regular-expression engines (which compile patterns to small programs), shells, emulators, smart contracts, plug-in systems, and every managed language platform.
Course roadmap and setup The course moves through foundations (Modules 0–2), interpreter construction (3–7), bytecode and VMs (8–12), runtime services and production runtimes (13–22) and advanced implementation (23–28). You can do every exercise in your browser with the MiniLang visualizers; to run the production examples locally you need any recent Python 3, JDK,.NET SDK and Node.js — output will differ in version-specific details.
Reading code examples and execution traces Each runnable example names its language and environment. Output blocks are real. Traces show one step per line: the instruction or AST node, and the state after it. Listings marked *illustrative* are teaching notation, not output of a tool.
Key distinction table Transforms a program into another representation
C compiler emits object code; Java compiler emits class files
Executes program behaviour by processing a representation Tree-walk interpreter or bytecode interpreter
Instruction representation designed for a virtual machine or runtime
JVM bytecode,.NET IL, Python implementation bytecode Virtual machine (VM)
Abstract or implemented machine that executes a defined instruction model
JVM, CLR execution environment, WebAssembly runtime Services and machinery needed to execute programs
Object allocation, exceptions, libraries, loading, scheduling, GC Compiles selected code during execution Runtime compilation of hot methods Compiles before program execution
Native application compilation or ahead-of-time runtime compilation Read–eval–print loop interface Interactive language shell
These categories overlap. CPython compiles source to bytecode and then interprets it; a JVM interprets bytecode and JIT-compiles hot methods; .NET can JIT IL at run time or compile it ahead of time.
Why It Matters in Real Systems
The vocabulary decides how you reason about performance ("why is the first request slow?" — warm-up and JIT), portability ("will this .pyc work on another Python?" — no), security ("can this plug-in read files?" — the runtime decides) and debugging ("why does the stack trace show a line I did not write?" — inlining, generated code).
Key Terms
Syntax / semantics Form of programs / meaning of programs Executes a program representation at run time
Translates one representation into another Instructions for a virtual machine Services a running program depends on Compile during / before execution Specification / implementation The rules / a program that follows them
Diagram, Execution Trace or State Transition
Text equivalent: source text is parsed into an AST. From there a program can be executed directly by a tree-walk interpreter, compiled to bytecode for a VM (which may interpret it and JIT-compile hot parts), or compiled ahead of time to machine code. Every path ends with machine instructions on the CPU — interpreters are themselves machine-code programs.
flowchart LR
S[Source text] --> C[Compiler front end]
C --> AST[AST]
AST --> TW[Tree-walk interpreter]
AST --> BC[Bytecode compiler]
BC --> B[Bytecode]
B --> VM[VM interpreter]
B --> JIT[JIT compiler]
JIT --> MC[Machine code]
AST --> AOT[AOT compiler] --> MC
MC --> CPU[CPU]
VM --> CPU
TW --> CPUSyntax or Instruction Format
No dedicated source syntax; this is a runtime or architecture concept. The course uses MiniLang (grammar in the course overview) and the MiniLang bytecode format (Module 8).
Complete Example
The same tiny MiniLang program, run by the course's two back ends — the tree-walk interpreter and the bytecode compiler + VM. The build runs both and checks that they agree:
Output (MiniLang reference implementation, Node.js 24 — tree-walk and VM agree):
CPython also compiles before it executes, even in "interpreted" code — the compile built-in exposes that step (CPython 3.13.1):
let base = 40;
let adjustment = 2;
print(base + adjustment);source = "print(20 + 22)"
code = compile(source, "<demo>", "exec") # source text → code object (contains bytecode)
print(type(code).__name__, "named", code.co_filename)
print("bytecode is", len(code.co_code), "bytes long in this CPython version")
exec(code) # the VM executes the code objectcode named <demo>
bytecode is 20 bytes long in this CPython version
42Line-by-Line Explanation
MiniLang: two let declarations bind globals; print(base + adjustment) evaluates an expression and prints it. The tree-walk interpreter walks the AST; the VM executes nine bytecode instructions (shown in Module 1). Both print 42.
Python: compile runs CPython's parser and compiler and returns a code object; co_code holds its bytecode. exec hands the code object to the bytecode interpreter. The byte count is CPython-version specific — the point is that it exists.
Expected Output
42 for both MiniLang back ends; for Python, the output shown above (the bytecode length is specific to CPython 3.13.1).
Step-by-Step Runtime Behavior
Source text is read. 2. A lexer turns characters into tokens. 3. A parser builds an AST. 4. Either the AST is evaluated directly, or it is compiled to bytecode and run by a VM. 5. Output is produced by a runtime service (print), which ultimately calls the operating system.
Common Misconceptions
"Interpreted languages are not compiled." Most mainstream "interpreted" implementations compile to bytecode first.
"Bytecode is portable everywhere." JVM class files are portable across conforming JVMs; CPython .pyc files are tied to a CPython version.
"A language is fast or slow." Implementations are fast or slow for given workloads.
"The runtime is the VM." The VM is one part of a runtime.
Common Bugs and Failure Modes
Relying on implementation details (CPython bytecode, V8 tiers) as if they were language guarantees; shipping cached bytecode built for a different runtime version; assuming a REPL behaves exactly like a script (REPLs often print expression values and keep state between inputs).
Corrected or Safer Patterns
Rely on the language specification; regenerate caches per runtime version; state the runtime and version whenever you show bytecode or performance numbers.
Debugging and Inspection Techniques
python -c "import sys; print(sys.version)", java -version, dotnet --info, node -p process.versions identify the implementation. CPython's dis, javap -c and Node's --print-bytecode show bytecode (Modules 17–20).
Performance Considerations
Each layer trades start-up time for steady-state speed: interpreting starts fast, JIT-compiled code runs fast after warm-up, AOT code starts and runs fast but cannot specialise on run-time behaviour.
Security Considerations
Runtimes enforce safety properties (type safety, memory safety, sandboxing) only for code they control; native extensions and FFI bypass them (Module 24).
Portability and Version Caveats
Specifications evolve slowly; implementations change every release. Bytecode, tiers and timings in this course are version-specific and labelled.
Real-World Use Cases
Choosing a deployment model (native binary, container with a runtime, serverless function), diagnosing cold starts, embedding a scripting language in an application, sandboxing user plug-ins.
Best Practices
Name the implementation and version; separate language guarantees from engine behaviour; measure rather than assume.
Exercises
E1 · Beginner. Classify each as compiler, interpreter, VM or runtime library: javac, the JVM's garbage collector, System.out.println, CPython's eval loop.
Answer: javac — compiler (source → class files). JVM garbage collector — part of the runtime engine. System.out.println — runtime (class) library. CPython's eval loop — the bytecode interpreter at the heart of CPython's VM.
E2 · Intermediate. Explain why "Python is interpreted" is an incomplete statement.
Answer: CPython compiles source to bytecode (code objects) and then interprets that bytecode; PyPy JIT-compiles to machine code; "interpreted" describes an implementation strategy, not the language.
E3 · Advanced challenge. List three runtime services a C program uses even without a VM.
Answer: Start-up code that sets up the stack and calls main (crt0), the C standard library (I/O, malloc/free), and dynamic linking/loading of shared libraries by the OS loader.
Quiz
? Which statement about compilers is correct?
- A compiler always produces machine code. :: javac produces JVM bytecode; the C# compiler produces CIL.
+ A compiler transforms a program into another representation. :: That representation can be bytecode, IR, another language or machine code.
- A compiler runs the program. :: Running is done by a CPU, interpreter or VM.
- Interpreted languages have no compiler. :: CPython compiles to bytecode first.
? What is a REPL?
+ An interactive read–eval–print loop interface. :: Behind it may be an interpreter, a VM or a JIT.
- A kind of bytecode. :: It is an interface, not an instruction format.
- A garbage collector. :: Unrelated.
- A specification. :: It is a tool.
? Which are implementation details rather than language guarantees? (select all that apply)
+ CPython's bytecode instructions :: They change between CPython versions.
+ V8's JIT tiers :: Engine-specific and changing.
- The meaning of `+` on two Java ints :: The Java Language Specification defines it.
+ The layout of objects in memory in a JVM :: Not specified by the Java language.Summary and Key Takeaways
Compilers translate, interpreters execute representations, bytecode targets VMs, runtimes provide services, and JIT/AOT describe *when* compilation happens. Real implementations combine these strategies. Always separate what a language specifies from what one implementation happens to do.
Further Reading
Robert Nystrom, *Crafting Interpreters* (free online) — the tree-walk and bytecode design used here follows its structure.
Aho, Lam, Sethi & Ullman, *Compilers: Principles, Techniques, and Tools*, ch. 1.
*The Java Virtual Machine Specification* (Java SE 21), ch. 1–2; *Python Language Reference*, "Execution model".