CAComputer Architecture · Lesson 4 of 7

From Source Code to Machine Code

You write text; the CPU eats binary instructions. The journeys differ — compiled ahead of time (C, Rust, Go), compiled just-in-time (JavaScript, Java), or interpreted (pure Python) — with very different performance consequences.

Ahead-of-time compilation (C, C++, Rust, Go, Swift): a compiler translates the whole program to machine code once, before it runs. The binary is native instructions; startup is instant and speed is maximal, but the binary is built for one CPU architecture and OS.

Interpretation (classic Python, Ruby): a program (the interpreter) reads your code — usually pre-parsed into simple bytecode — and performs each operation itself. A bytecode 'ADD' might cost the interpreter 50+ real instructions of dispatching, type checking, and boxing. Flexible and portable, 10-100x slower for tight loops.

Just-in-time (JIT) compilation (JavaScript's V8, the JVM, C#, PyPy): start by interpreting, watch which functions run hot, compile those to native machine code at runtime — optimizing for the types actually seen. That's how JavaScript went from toy speed to within a few x of C for many workloads.

Text
The classic AOT pipeline (detailed in the Compilers track):

 source.c
   -> preprocessor  (expand #include, #define)
   -> compiler      (C -> assembly, optimizations here)
   -> assembler     (assembly -> object file: machine code)
   -> linker        (combine .o files + libraries -> executable)
 ./program

Rough single-thread speed for a numeric loop:
  C / Rust (AOT)      1x
  JS / Java (JIT)     1-3x slower
  Python (interp)     30-100x slower
  (NumPy escapes by calling AOT-compiled C under the hood)
◆ Note
Architectures matter: x86-64 (Intel/AMD desktops, most servers) and ARM64 (every phone, Apple Silicon, AWS Graviton) speak different instruction sets. A binary for one can't run natively on the other — that's why Apple built Rosetta 2, a translator, for the Intel-to-M1 transition.