Home / Software y Cloud / Compilers from the inside: how your code becomes machine instructions

Compilers from the inside: how your code becomes machine instructions

Ilustración del pipeline de un compilador

When you write a program in Python, JavaScript or C++, nobody executes that text directly: a computer only understands binary instructions. Between your code and the processor sits a piece of software we almost never see, yet it lies at the heart of all computing: the compiler. Let’s open it up and see, step by step, how it turns a language readable by humans into something the machine can run.

Step one: turning text into tokens

Everything starts with the lexical analyzer (or lexer). Its job is to read the text file character by character and group it into minimal units of meaning, called tokens: identifiers, numbers, operators, reserved words such as if or return, and symbols like braces or parentheses. It is like splitting a sentence into words before trying to understand it. Each token is tagged with its type, so int x = 5; becomes a sequence of tokens: reserved word, identifier, assignment operator and number.

The syntax tree: the grammar of code

With that list of tokens, the syntax analyzer (or parser) applies the language’s grammar to build an abstract syntax tree (AST). The AST is a hierarchical representation of the program: each node is an operation or construct, and its children are its operands or subexpressions. For example, a + b * c becomes a tree where the multiplication hangs below the addition, reflecting operator precedence. If the code does not follow the grammar, this is where the compiler throws the classic syntax error.

Semantic analysis: making the pieces meaningful

Correct syntax is not enough. The semantic analysis walks the AST and checks meaning: that variables are declared before use, that types match in every operation (you cannot add an integer to a string) and that functions are called with the right number of arguments. In many compilers this step produces an “annotated” version of the AST, carrying type information, which will serve as the basis for the following phases.

The intermediate representation: the bridge language

Instead of going straight from the tree to machine code, modern compilers generate an intermediate representation (IR): a low-level language, independent of any specific CPU, that describes the program as a list of instructions over variables. The IR is where most optimizations happen: removing redundant computations, hoisting operations that do not change inside a loop outside of it, or taking advantage of the fact that some multiplications are really bit shifts. Optimizing over the IR lets the same logic serve different processors.

Code generation and the linker

The compiler’s final phase is code generation: translating the optimized IR into the real instructions of the target processor (x86, ARM, RISC-V), assigning CPU registers and deciding execution order. The result is usually an object file. This is where the linker comes in, joining several object files with the needed libraries and resolving references between symbols to produce the final executable. Without it, the code would not know where to find, for example, the printf function.

Interpreters and just-in-time compilation

Not everything is compiled ahead of time. Interpreters (such as Python in its classic mode or the early JavaScript ones) run the code directly, without producing an intermediate binary, which makes them more flexible but usually slower. Modern engines, such as V8 (Chrome’s and Node.js’s), use just-in-time compilation (JIT): they first interpret the code to start up fast and, while the program runs, detect functions that are called very often and compile them to machine code on the fly. It is a trade-off between the interpreter’s fast startup and the performance of compiled code.

The frontier: from compiling to translating

Understanding how a compiler works is also the foundation of tools you use every day without thinking. The transpiler that turns TypeScript into JavaScript, the one that bundles and minifies your website, or the one that translates an artificial intelligence model into optimized instructions for a GPU: they are all specialized compilers. When you read that one language “is faster” than another, you are largely comparing the quality of the compiler behind it, not just the language itself.