Skip to content

Split giant VM type-switch functions into per-node methods - #376

Merged
mattn merged 1 commit into
masterfrom
refactor-split-vm-dispatch
Jul 10, 2026
Merged

Split giant VM type-switch functions into per-node methods#376
mattn merged 1 commit into
masterfrom
refactor-split-vm-dispatch

Conversation

@mattn

@mattn mattn commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Summary

The four largest functions in the VM held all their logic inside a single giant type switch:

Function Before After
invokeExpr (vmExpr.go) 787 lines 113-line dispatcher + 17 methods
runSingleStmt (vmStmt.go) 767 lines 100-line dispatcher + 16 methods
invokeLetExpr (vmLetExpr.go) 378 lines 33-line dispatcher + 7 methods
invokeOperator (vmOperator.go) 279 lines 25-line dispatcher + 4 methods

The type switches now only dispatch; each case body moved into its own method taking the typed AST node (e.g. invokeArrayExpr(expr *ast.ArrayExpr)), matching the existing funcExpr/callExpr naming. Two hotspots are split one level further:

  • ForStmtrunForSliceStmt / runForMapStmt / runForChanStmt
  • let ItemExprinvokeLetItemSlice / invokeLetItemMap / invokeLetItemString

This is a pure code move — no behavior change. The defer recoverFunc(runInfo) panic capture in ChanExpr/CloseStmt keeps its effective scope, and the for-loops still preserve rv on ErrReturn.

Benchmarks

No regression — everything got faster (benchstat, n=6): the much smaller stack frames make the recursive dispatch cheaper.

ArithmeticInt-16    696.9n ±  4%   572.4n ± 5%  -17.85% (p=0.002 n=6)
ArithmeticFloat-16  767.5n ±  5%   638.8n ± 2%  -16.77% (p=0.002 n=6)
Comparison-16       1.256µ ± 23%   1.060µ ± 5%  -15.61% (p=0.002 n=6)
Loop-16             35.77µ ± 17%   25.30µ ± 2%  -29.26% (p=0.002 n=6)
Fibonacci-16        1.516m ±  5%   1.265m ± 6%  -16.53% (p=0.002 n=6)
StringConcat-16     877.9n ±  2%   772.5n ± 4%  -12.01% (p=0.002 n=6)
VarAccess-16        1.360µ ±  6%   1.209µ ± 5%  -11.10% (p=0.002 n=6)
MapAccess-16        1.912µ ±  5%   1.700µ ± 6%  -11.11% (p=0.002 n=6)
ArrayAccess-16      1.388µ ±  9%   1.150µ ± 4%  -17.15% (p=0.002 n=6)
FuncCall-16         1.341µ ±  4%   1.231µ ± 4%   -8.24% (p=0.002 n=6)
geomean                                          -12.10%

Testing

  • go build ./..., go vet ./vm/
  • go test ./... — all packages pass
  • go test -race ./vm/ — pass

invokeExpr (787 lines), runSingleStmt (767), invokeLetExpr (378) and
invokeOperator (279) each held their entire logic in a single type
switch. Keep the switches as thin dispatchers and move each case body
into its own method taking the typed AST node, matching the existing
funcExpr/callExpr style. ForStmt's three loop kinds and let-ItemExpr's
three item kinds are split one level further.

No behavior change. Benchmarks improve across the board (geomean -12%,
Loop -29%) since the smaller stack frames make the recursive dispatch
cheaper.
@mattn
mattn merged commit 4a926db into master Jul 10, 2026
12 checks passed
@mattn
mattn deleted the refactor-split-vm-dispatch branch July 10, 2026 00:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant