Yasir Explains/Compiler Design/Intermediate Code Generation/Run-Time Environment
Intermediate Code Generation

Run-Time Environment

On this page

Definition: The Run-Time EnvironmentKey VocabularyWhat the Compiler Must ArrangeRoadmapKey Points
Intermediate Code Generation

Run-Time Environment

The data structures and conventions a compiler sets up so the generated program can run correctly — managing names, scopes, procedure activations, and memory. Introduced through one small recursive program, fib.

Definition: The Run-Time Environment

Run-time environment: the set of data structures and conventions the compiler establishes — and the generated code relies on — so the program executes correctly at run time. It manages names, scopes, procedure activations, and memory.

The source program talks about names, procedures, and scopes. The hardware knows only registers and a flat array of memory addresses. The run-time environment is the bridge: a collection of agreed-upon rules that say where each piece of data lives, how a procedure is called, and when storage is created and destroyed.

Crucially, the compiler does not run the program — it only arranges for it to run. It bakes these conventions into the code it emits, so that at run time everything lines up.


The running program

We use one small program throughout this chapter:

Example
int main() {
int x = fib(3);
print(x);
}
int fib(int n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}

It is recursive, so the same procedure fib is "running" several times at once — the perfect stress test for a run-time environment. (For reference, fib(3) = 2.)

Key Vocabulary

Four terms recur for the rest of the chapter. Each has a one-line definition.

TermOne-line definition
NameA textual identifier in the source, such as n or fib.
Data object (storage)The actual chunk of memory a name refers to while the program runs.
ProcedureA named, parameterized piece of code — written once (e.g. fib).
Activation of a procedureOne execution of that procedure for a particular call.
Lifetime of an activationThe span it is alive — from the moment it is called until it returns.
Scope of a nameThe region of source text where that name refers to a given data object.

The key distinction: a procedure is written once, but each call creates a brand-new activation, and each activation gets its own copy of the parameters and local variables.


One procedure, many activations

fib is one procedure. But running fib(3) calls it many times, and each call is a separate activation with its own n:

Example
fib(3) ── activation A ── its own n = 3
├─ fib(2) ── activation B ── its own n = 2
│ ├─ fib(1) ── activation C ── its own n = 1
│ └─ fib(0) ── activation D ── its own n = 0
└─ fib(1) ── activation E ── its own n = 1

Five distinct activations of the same procedure. Activations B and E both run fib with the argument 1, yet they are different activations: each has its own instance of n, born when that call starts and gone when it returns. If all activations shared one n, recursion would be impossible — the inner call would clobber the outer call's value.

What the Compiler Must Arrange

To make those activations work, the compiler arranges three things.

1. Storage layout for each activation. Every activation needs room for its parameters, local variables, the return value, and bookkeeping (such as where to return to). The compiler decides this layout — what sits at what offset — so it can later emit code that reads and writes the right slots. For fib, each activation needs a slot for n and a place to build its return value.

2. The calling sequence. A fixed protocol splits work between caller and callee:

SideResponsibility
CallerEvaluate the arguments, save its own state (e.g. registers, return address), then jump to the callee.
CalleeAllocate space for its locals, run the body, place the return value where the caller expects it, then restore state and return.

Both sides must agree on this protocol exactly, since they are usually compiled independently. When main runs fib(3), main is the caller and fib the callee — and inside fib, each recursive fib(...) makes fib a caller in turn.

3. Name binding and scope. Inside fib's body, the name n must resolve to this activation's instance of n, not some other call's. The compiler establishes the binding rules so every use of a name reaches the correct data object.

Roadmap

These activations are organized conceptually as an activation tree, tracked at run time by a control stack, and laid out in run-time storage by an allocation strategy. Each is the subject of an upcoming topic.

We have already glimpsed the activation tree (the fib call diagram above). The next topics give each of these its own treatment — for now, just note the names.

Key Points

  • The run-time environment is the set of data structures and conventions the compiler sets up so the generated program runs correctly — managing names, scopes, activations, and memory.
  • A procedure is code written once; each call creates a distinct activation with its own parameters and locals.
  • An activation is alive from call to return — that span is its lifetime.
  • Recursion makes the point concrete: running fib(3) creates several activations of the same fib, each with its own instance of n.
  • The compiler must arrange three things: storage layout per activation, the calling sequence (caller saves state and passes args; callee runs, returns a value, restores state), and name binding / scope.
  • Activations are organized as an activation tree, tracked by a control stack, and placed by an allocation strategy — the topics ahead.