From 04a9b16f3d69aa66f3aaab44dcd322e4a02a82aa Mon Sep 17 00:00:00 2001
From: thepudds <thepudds1460@gmail.com>
Date: Wed, 12 Mar 2025 14:45:17 -0400
Subject: [PATCH] cmd/compile/internal/escape: avoid reading ir.Node during
 inner loop of walkOne

Broadly speaking, escape analysis has two main phases. First, it
traverses the AST while building a data-flow graph of locations and
edges. Second, during "solve", it repeatedly walks the data-flow graph
while carefully propagating information about each location, including
whether a location's address reaches the heap.

Once escape analysis is in the solve phase and repeatedly walking the
data-flow graph, almost all the information it needs is within the
location graph, with a notable exception being the ir.Class of an
ir.Name, which currently must be checked by following a pointer from
the location to its ir.Node.

For typical graphs, that does not matter much, but if the graph becomes
large enough, cache misses in the inner solve loop start to matter more,
and the class is checked many times in the inner loop.

We therefore store the class information on the location in the graph
to reduce how much memory we need to load in the inner loop.

The package github.com/microsoft/typescript-go/internal/checker
has many locations, and compilation currently spends most of its time
in escape analysis.

This CL gives roughly a 30% speedup for wall clock compilation time
for the checker package:

  go1.24.0:      91.79s
  this CL:       64.98s

Linux perf shows a healthy reduction for example in l2_request.miss and
dTLB-load-misses on an amd64 test VM.

We could tweak things a bit more, though initial review feedback
has suggested it would be good to get this in as it stands.

Subsequent CLs in this stack give larger improvements.

Updates #72815

Change-Id: I3117430dff684c99e6da1e0d7763869873379238
Reviewed-on: https://go-review.googlesource.com/c/go/+/657295
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Jake Bailey <jacob.b.bailey@gmail.com>
Reviewed-by: David Chase <drchase@google.com>
---
 src/cmd/compile/internal/escape/graph.go | 7 +++++++
 src/cmd/compile/internal/escape/solve.go | 4 ++--
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/cmd/compile/internal/escape/graph.go b/src/cmd/compile/internal/escape/graph.go
index 75e2546a7b7..cd800bc4d65 100644
--- a/src/cmd/compile/internal/escape/graph.go
+++ b/src/cmd/compile/internal/escape/graph.go
@@ -75,6 +75,8 @@ type location struct {
 	captured   bool // has a closure captured this variable?
 	reassigned bool // has this variable been reassigned?
 	addrtaken  bool // has this variable's address been taken?
+	param      bool // is this variable a parameter (ONAME of class ir.PPARAM)?
+	paramOut   bool // is this variable an out parameter (ONAME of class ir.PPARAMOUT)?
 }
 
 type locAttr uint8
@@ -281,6 +283,11 @@ func (e *escape) newLoc(n ir.Node, persists bool) *location {
 		curfn:     e.curfn,
 		loopDepth: e.loopDepth,
 	}
+	if loc.isName(ir.PPARAM) {
+		loc.param = true
+	} else if loc.isName(ir.PPARAMOUT) {
+		loc.paramOut = true
+	}
 	if persists {
 		loc.attrs |= attrPersists
 	}
diff --git a/src/cmd/compile/internal/escape/solve.go b/src/cmd/compile/internal/escape/solve.go
index 2002f2fbe41..4b0db1884d7 100644
--- a/src/cmd/compile/internal/escape/solve.go
+++ b/src/cmd/compile/internal/escape/solve.go
@@ -126,7 +126,7 @@ func (b *batch) walkOne(root *location, walkgen uint32, enqueue func(*location))
 		// corresponding result parameter, then record
 		// that value flow for tagging the function
 		// later.
-		if l.isName(ir.PPARAM) {
+		if l.param {
 			if b.outlives(root, l) {
 				if !l.hasAttr(attrEscapes) && (logopt.Enabled() || base.Flag.LowerM >= 2) {
 					if base.Flag.LowerM >= 2 {
@@ -270,7 +270,7 @@ func (b *batch) outlives(l, other *location) bool {
 	// We don't know what callers do with returned values, so
 	// pessimistically we need to assume they flow to the heap and
 	// outlive everything too.
-	if l.isName(ir.PPARAMOUT) {
+	if l.paramOut {
 		// Exception: Closures can return locations allocated outside of
 		// them without forcing them to the heap, if we can statically
 		// identify all call sites. For example:
-- 
GitLab