Building a respectable compiler requires basic blocks in order for liveness analysis to be performant. Consider my own Apple compiler:

Compilation was dominated by mkLiveness and moreover simple functions took tens of milliseconds to compile.

benchmarking pipeline/bytes (gamma) time 62.52 ms (62.14 ms .. 62.86 ms) 1.000 R² (1.000 R² .. 1.000 R²) mean 62.54 ms (62.29 ms .. 62.80 ms) std dev 470.3 μs (308.3 μs .. 657.9 μs)

benchmarking pipeline/bytes (fcdf) time 108.3 ms (106.6 ms .. 110.5 ms) 1.000 R² (0.999 R² .. 1.000 R²) mean 106.1 ms (105.4 ms .. 107.1 ms) std dev 1.288 ms (696.6 μs .. 2.067 ms)

After rewriting to use basic blocks, compilation times were less than 20ms, a far better user experience. Moreover, liveness analysis no longer dominates.

benchmarking pipeline/bytes (gamma) time 5.313 ms (5.171 ms .. 5.485 ms) 0.994 R² (0.991 R² .. 0.997 R²) mean 5.166 ms (5.087 ms .. 5.262 ms) std dev 267.0 μs (218.5 μs .. 321.2 μs) variance introduced by outliers: 29% (moderately inflated)

benchmarking pipeline/bytes (fcdf) time 10.42 ms (9.838 ms .. 10.96 ms) 0.982 R² (0.971 R² .. 0.991 R²) mean 12.13 ms (11.71 ms .. 12.55 ms) std dev 1.090 ms (907.0 μs .. 1.409 ms) variance introduced by outliers: 48% (moderately inflated)

Appel mentions basic blocks as an advanced optimization but does not fill in details; the precise implementation in Haskell is instructive and elegant.

First, we define uBB, dBB to simulate uses and defs on a basic block:

uBB, dBB :: E reg => [X86 reg freg a] -> IS.IntSet uBB = foldr (\p n -> uses p `IS.union` (n IS.\\ defs p)) IS.empty dBB = foldMap defs

Note that foldr processes instructions in reverse execution order.

Then, to get liveness information for each instruction, we apply the liveness equations (each instruction has one successor in a basic block):

\( {\tt in}[n]={\tt uses}[n]\cup ({\tt out}[n]~\backslash~{\tt defs}[n])\)

\( {\tt out}[n]={\tt in}[succ(n)]\)

Using the live-out information for the basic block as an initial value, we proceed in reverse order:

data Liveness = Liveness { ins :: !IS.IntSet, out :: !IS.IntSet, fins :: !IS.IntSet, fout :: !IS.IntSet } deriving (Eq)

data BB arch reg freg a b = BB { unBB :: [arch reg freg a], caBB :: b }

expand :: (E reg, E freg) => BB X86 reg freg () Liveness -> [X86 reg freg Liveness] expand (BB asms@(_:_) li) = scanr (\n p -> lN n (ann p)) lS iasms where lN a s = let ai=uses a <> (ao IS.\\ defs a) ao=ins s aif=usesF a <> (aof IS.\\ defsF a) aof=fins s in a $> Liveness ai ao aif aof lS = let ao=out li aof=fout li in asm $> Liveness (uses asm `IS.union` (ao `IS.difference` defs asm)) ao (usesF asm `IS.union` (aof `IS.difference` defsF asm)) aof (iasms, asm) = (init asms, last asms) expand _ = []