diff options
| author | Cherry Mui <cherryyz@google.com> | 2025-05-21 14:33:13 -0400 |
|---|---|---|
| committer | Cherry Mui <cherryyz@google.com> | 2025-05-21 20:07:36 -0700 |
| commit | 5e6a868b28d3e7a71fa328c18ff5e93d72a1fb67 (patch) | |
| tree | 6d026379ac2e2544116d72cddab70ef935418e6b /src/cmd/compile | |
| parent | 8bf816ae6879fa4537cc6e6e292769df2d7dbb78 (diff) | |
| download | go-5e6a868b28d3e7a71fa328c18ff5e93d72a1fb67.tar.xz | |
cmd/compile, unique: model data flow of non-string pointers
Currently, hash/maphash.Comparable escapes its parameter if it
contains non-string pointers, but does not escape strings or types
that contain strings but no other pointers. This is achieved by a
compiler intrinsic.
unique.Make does something similar: it stores its parameter to a
central map, with strings cloned. So from the escape analysis's
perspective, the non-string pointers are passed through, whereas
string pointers are not. We currently cannot model this type of
type-dependent data flow directly in Go. So we do this with a
compiler intrinsic. In fact, we can unify this and the intrinsic
above.
Tests are from Jake Bailey's CL 671955 (thanks!).
Fixes #73680.
Change-Id: Ia6a78e09dee39f8d9198a16758e4b5322ee2c56a
Reviewed-on: https://go-review.googlesource.com/c/go/+/675156
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Jake Bailey <jacob.b.bailey@gmail.com>
Diffstat (limited to 'src/cmd/compile')
| -rw-r--r-- | src/cmd/compile/internal/escape/call.go | 12 | ||||
| -rw-r--r-- | src/cmd/compile/internal/inline/inl.go | 17 | ||||
| -rw-r--r-- | src/cmd/compile/internal/walk/expr.go | 4 |
3 files changed, 18 insertions, 15 deletions
diff --git a/src/cmd/compile/internal/escape/call.go b/src/cmd/compile/internal/escape/call.go index a80e2707e2..58c44eb9bb 100644 --- a/src/cmd/compile/internal/escape/call.go +++ b/src/cmd/compile/internal/escape/call.go @@ -84,15 +84,19 @@ func (e *escape) call(ks []hole, call ir.Node) { argument(e.tagHole(ks, fn, param), arg) } - // hash/maphash.escapeForHash forces its argument to be on - // the heap, if it contains a non-string pointer. We cannot + // internal/abi.EscapeNonString forces its argument to be on + // the heap, if it contains a non-string pointer. + // This is used in hash/maphash.Comparable, where we cannot // hash pointers to local variables, as the address of the // local variable might change on stack growth. // Strings are okay as the hash depends on only the content, // not the pointer. + // This is also used in unique.clone, to model the data flow + // edge on the value with strings excluded, because strings + // are cloned (by content). // The actual call we match is - // hash/maphash.escapeForHash[go.shape.T](dict, go.shape.T) - if fn != nil && fn.Sym().Pkg.Path == "hash/maphash" && strings.HasPrefix(fn.Sym().Name, "escapeForHash[") { + // internal/abi.EscapeNonString[go.shape.T](dict, go.shape.T) + if fn != nil && fn.Sym().Pkg.Path == "internal/abi" && strings.HasPrefix(fn.Sym().Name, "EscapeNonString[") { ps := fntype.Params() if len(ps) == 2 && ps[1].Type.IsShape() { if !hasNonStringPointers(ps[1].Type) { diff --git a/src/cmd/compile/internal/inline/inl.go b/src/cmd/compile/internal/inline/inl.go index e3480c2463..8bba604214 100644 --- a/src/cmd/compile/internal/inline/inl.go +++ b/src/cmd/compile/internal/inline/inl.go @@ -454,6 +454,11 @@ opSwitch: // generate code. cheap = true } + if strings.HasPrefix(fn, "EscapeNonString[") { + // internal/abi.EscapeNonString[T] is a compiler intrinsic + // implemented in the escape analysis phase. + cheap = true + } case "internal/runtime/sys": switch fn { case "GetCallerPC", "GetCallerSP": @@ -472,12 +477,6 @@ opSwitch: case "panicrangestate": cheap = true } - case "hash/maphash": - if strings.HasPrefix(fn, "escapeForHash[") { - // hash/maphash.escapeForHash[T] is a compiler intrinsic - // implemented in the escape analysis phase. - cheap = true - } } } // Special case for coverage counter updates; although @@ -801,10 +800,10 @@ func inlineCallCheck(callerfn *ir.Func, call *ir.CallExpr) (bool, bool) { } } - // hash/maphash.escapeForHash[T] is a compiler intrinsic implemented + // internal/abi.EscapeNonString[T] is a compiler intrinsic implemented // in the escape analysis phase. - if fn := ir.StaticCalleeName(call.Fun); fn != nil && fn.Sym().Pkg.Path == "hash/maphash" && - strings.HasPrefix(fn.Sym().Name, "escapeForHash[") { + if fn := ir.StaticCalleeName(call.Fun); fn != nil && fn.Sym().Pkg.Path == "internal/abi" && + strings.HasPrefix(fn.Sym().Name, "EscapeNonString[") { return false, true } diff --git a/src/cmd/compile/internal/walk/expr.go b/src/cmd/compile/internal/walk/expr.go index 96087e16b7..6775bc4fc8 100644 --- a/src/cmd/compile/internal/walk/expr.go +++ b/src/cmd/compile/internal/walk/expr.go @@ -594,8 +594,8 @@ func walkCall(n *ir.CallExpr, init *ir.Nodes) ir.Node { if n.Op() == ir.OCALLFUNC { fn := ir.StaticCalleeName(n.Fun) - if fn != nil && fn.Sym().Pkg.Path == "hash/maphash" && strings.HasPrefix(fn.Sym().Name, "escapeForHash[") { - // hash/maphash.escapeForHash[T] is a compiler intrinsic + if fn != nil && fn.Sym().Pkg.Path == "internal/abi" && strings.HasPrefix(fn.Sym().Name, "EscapeNonString[") { + // internal/abi.EscapeNonString[T] is a compiler intrinsic // for the escape analysis to escape its argument based on // the type. The call itself is no-op. Just walk the // argument. |
