Register Allocation
debt(d7/e5/b3/t5)
Closest to 'only careful code review or runtime testing' (d7), spill traffic is invisible without profiling via perf or llvm-mca; no linter flags register pressure.
Closest to 'touches multiple files / significant refactor in one component' (e5), the quick_fix says shrink hot loops and minimise live variables, which typically means refactoring hot functions, splitting them, and reducing cross-call live state.
Closest to 'localised tax' (b3), register pressure concerns weigh on hot paths in performance-sensitive components but don't shape the overall PHP application architecture.
Closest to 'notable trap most devs eventually learn' (t5), the misconception (allocation is just picking registers, ignoring spills/splits/coalescing) plus confusion with register renaming and the dead `register` keyword are documented gotchas.
Also Known As
TL;DR
Explanation
Linear scan (Poletto & Sarkar, 1999) trades some code quality for compile speed by walking live ranges in order of start point and maintaining an active set — this is what most JITs use, including PHP 8's JIT (a linear-scan allocator feeding code emission through DynASM), V8's Crankshaft (historically), and LLVM's fast register allocator.
Common Misconception
Why It Matters
Common Mistakes
- Assuming the compiler will always keep loop counters in registers — without enough free registers or with address-taken variables, they may spill to the stack.
- Confusing register allocation with register renaming — renaming is a CPU micro-architectural feature, allocation is a compile-time decision.
- Believing more registers always means faster code — beyond a working-set threshold, extra registers do not help because allocation already fits the live set.
- Writing huge functions with many local variables and expecting hot paths to stay in registers — large live sets force spills even with good allocators.
- Assuming hints like C's `register` keyword influence modern compilers — they are ignored; allocation is fully automatic and profile-driven.
Code Examples
// Conceptual: function with huge live set defeats register allocator.
// Each variable below is live for the entire function body,
// forcing the allocator to spill most of them to the stack on x86_64.
function hotLoop(array $data): float {
$a = 0.0; $b = 0.0; $c = 0.0; $d = 0.0;
$e = 0.0; $f = 0.0; $g = 0.0; $h = 0.0;
$i = 0.0; $j = 0.0; $k = 0.0; $l = 0.0;
$m = 0.0; $n = 0.0; $o = 0.0; $p = 0.0;
foreach ($data as $x) {
$a += $x; $b += $x*2; $c += $x*3; $d += $x*4;
$e += $x*5; $f += $x*6; $g += $x*7; $h += $x*8;
$i += $x*9; $j += $x*10; $k += $x*11; $l += $x*12;
$m += $x*13; $n += $x*14; $o += $x*15; $p += $x*16;
}
return $a+$b+$c+$d+$e+$f+$g+$h+$i+$j+$k+$l+$m+$n+$o+$p;
}
// Split work so each loop has a small live set the JIT can keep in registers.
function hotLoop(array $data): float {
return sumWeighted($data, 1, 8) + sumWeighted($data, 9, 16);
}
function sumWeighted(array $data, int $start, int $end): float {
$total = 0.0; // only $total and $i are live across iterations
foreach ($data as $x) {
for ($i = $start; $i <= $end; $i++) {
$total += $x * $i; // fits comfortably in physical registers
}
}
return $total;
}