JIT Compilation
Compiling with information a static compiler cannot have. Tiers, profiling, speculation, guards, and the deoptimization that catches a wrong guess.
Start the program immediately by interpreting it, watch which code actually runs, and compile that code to native instructions while the program is still running — using facts about this execution that no ahead-of-time compiler could have had.
A static compiler must be correct for every type that could occur, every branch that could be taken and every target a call could reach. A JIT sees which ones actually occur, and specializing to the actual case is worth far more than any amount of extra analysis on the general one.
Not one compiler but several, arranged from instant-and-slow to expensive-and-fast, with code promoted upward as it proves hot and demoted back down when a speculation fails. Startup and steady state stop competing for the same knob.
Deciding what to compile is a measurement problem with a cost on both sides: compile too eagerly and you spend time on code that never repays it, compile too late and the program runs slowly through the window where it mattered most.
A function that was entered once and has been looping for a minute cannot benefit from being compiled, because nothing will call it again. On-stack replacement swaps the running activation itself over to optimized code mid-loop, which means translating a live frame from one code version's layout into another's.
"This value has been a small integer every time, so compile an integer fast path." The profile is evidence, not proof — which is exactly why the fast path is preceded by a check, and why the whole apparatus of guards and deoptimization exists behind it.
The cheap runtime check that turns an assumption into a sound one. A guard is a comparison, a branch and a piece of metadata — and its cost is the bar every speculation has to clear before it is worth making.
A guard fails, and execution must continue correctly in code that assumed nothing — which means reconstructing an interpreter frame from an optimized one. Keeping that reconstruction possible is a standing obligation, and the obligation, not the mechanism, is what this lesson is about.
Cache the resolved answer at the call site itself, guarded by a check of the key that produced it. One target is monomorphic and nearly free; a few is polymorphic and still cheap; many is megamorphic, and the right response is to stop caching rather than to cache harder.
Compilation on the user's critical path, a warmup period where the program is measurably slower than itself, memory for code and profiles, benchmark numbers that will not sit still, and a writable-then-executable memory region that some platforms refuse to allow at all.