Step 05 · mem2reg + the O2 pipeline

Our lowering is deliberately naïve: every named local becomes an alloca i64 in the entry block, every read is a load, every write a store. We do not try to construct SSA in the front end.

That's fine, because O2 includes mem2reg (a.k.a. PromoteMemToReg), which:

  1. Finds allocas whose only uses are direct loads/stores.
  2. Replaces them with proper SSA values, inserting Φ-nodes where control flow joins.

After mem2reg, downstream passes can do real work:

  • instcombine — peephole rewrites.
  • gvn — global value numbering deduplicates.
  • simplifycfg — collapses trivial branches.
  • licm — hoists loop invariants.
  • loop-unroll, loop-vectorize, slp-vectorize — where profitable.
  • globalopt — turns module-private mutable globals into constants when only initialised once.

Observable

let x = 7;
print x;

Pre--O:

@x = global i64 0
…
store i64 7, ptr @x
%0 = load i64, ptr @x
call i32 @printf(ptr @.fmt, i64 %0)

Post--O (test test_mem2reg_after_opt_eliminates_allocas asserts this):

call i32 @printf(ptr @.fmt, i64 7)

Lesson

Front-ends do not need a smart code generator. Emit straightforward load/store-heavy IR; let mem2reg + the rest of O2 turn it into great machine code. This is the LLVM superpower.