SSA Backend — Tasks¶
Hands-on exercises. Each is concrete and verifiable with the standard toolchain (no special build of Go). Work in a scratch module; keep a notebook of what each pass column showed. Helper commands:
GOSSAFUNC=Fn go build . && open ssa.html # xdg-open on Linux
go build -gcflags='-d=ssa/check_bce/debug=1' . # surviving bounds checks
go build -gcflags='-m -m' . # inlining + escape
go build -gcflags=-S . 2>asm.s # final assembly
-
Dump your first function. Write
func Add(a, b int) int { return a + b }, runGOSSAFUNC=Add go build, openssa.html. Identify theArgvalues foraandb, theAdd64, and the column whereAdd64becomes an arch op. -
Trace one value across passes. In the dump above, click the result value and note every column it appears in. Record where it changes op name (the
lowercolumn). -
Watch a bounds check disappear. Write
func sum(s []int) (t int) { for i := range s { t += s[i] } ; return }. Find theIsInBoundsvalue in theoptcolumn and confirm it is gone in theprovecolumn. -
Confirm BCE with the debug flag. Run
check_bce/debug=1on thesumabove and verify it prints nothing for thes[i]line. Then changerange sto afor i := 0; i <= len(s); i++loop and watch the flag report the surviving check. -
Make a hoist fix. Write a function reading
s[0]..s[3]with the length guard after the reads. Confirm checks survive, move the guard (if len(s) < 4) to the top, and confirm all four checks now fold. -
Pin a masked index. Write
func bucket(t []int, h uint) int { return t[h&0xff] }. Show the check survives. Addt = t[:256]and show it folds (this exercises the(IsInBounds (And ...) (Const ...))rule). -
Find a phi. Write a function with
if cond { x = 10 } else { x = 20 }; return x. Inssa.htmllocate thePhivalue at the merge block and confirm it has two args, one per predecessor edge. -
See nil-check elimination. Write
func f(p *T) int { return p.x + p.y }. In thenilcheckelimcolumn confirm the secondNilCheckis removed. Then break it by round-trippingpthrough ananybetween the reads and observe the change. -
Observe constant folding. Write
func k() int { return 2*3 + 4 }. Confirm noADD/IMULsurvives — it's a singleConstby theoptcolumn. -
Catch a missed CSE. Write two calls to the same impure function separated by another call; read
-Sand confirm both calls are emitted. Then make the value pure (a local arithmetic expression) and confirm CSE merges it. -
Compare two implementations. Implement the same routine two ways (e.g. index loop vs
rangeloop). Dump both withGOSSAFUNC, diff theproveandgenssacolumns, and write down which produced fewer values/branches. -
Read the assembly for a write barrier. Write a loop that stores
&nodes[i]into a[]*Node. Findruntime.gcWriteBarrierin-S. Rewrite to store indices and confirm the barrier disappears. -
Disable a pass for diagnosis. Build the
sumfunction with-gcflags='-d=ssa/prove/off=1'and confirm the bounds check reappears in-S. Note that this is a diagnosis tool only, not a config. -
Find a lowering rule. Open
$(go env GOROOT)/src/cmd/compile/internal/ssa/_gen/AMD64.rules, locate the rule that lowers a genericAdd64toADDQ, and the matching op definition inAMD64Ops.go. Then find the corresponding generic constant-fold rule forAdd64ingeneric.rules.
Stretch: pick a function from a real dependency, run GOSSAFUNC on it, and write a one-paragraph report: which checks were eliminated, which survived and why, and one change that would help the optimizer.
Further reading¶
- SSA README & rules:
$(go env GOROOT)/src/cmd/compile/internal/ssa/README.md,_gen/generic.rules,_gen/AMD64.rules - Codegen test examples:
$(go env GOROOT)/test/codegen/ - Go wiki Compiler Optimizations: https://go.dev/wiki/CompilerOptimizations