cmd/compile: determine static values of len and cap in make() calls

This change improves escape analysis by attempting to
deduce static values for the len and cap parameters,
allowing allocations to be made on the stack.

Change-Id: I1161019aed9f60cf2c2fe4d405da94ad415231ac
GitHub-Last-Rev: d78c1b4ca55fa53282e665009f689d0b013f1434
GitHub-Pull-Request: golang/go#71693
Reviewed-on: https://go-review.googlesource.com/c/go/+/649035
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
Auto-Submit: Keith Randall <khr@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
This commit is contained in:
Mateusz Poliwczak 2025-02-17 11:29:04 +00:00 committed by Gopher Robot
parent 28d7eec3a2
commit ba3f568988
4 changed files with 134 additions and 9 deletions

View File

@ -5,9 +5,12 @@
package escape package escape
import ( import (
"cmd/compile/internal/base"
"cmd/compile/internal/ir" "cmd/compile/internal/ir"
"cmd/compile/internal/typecheck" "cmd/compile/internal/typecheck"
"cmd/compile/internal/types" "cmd/compile/internal/types"
"go/constant"
"go/token"
) )
func isSliceSelfAssign(dst, src ir.Node) bool { func isSliceSelfAssign(dst, src ir.Node) bool {
@ -206,14 +209,28 @@ func HeapAllocReason(n ir.Node) string {
if n.Op() == ir.OMAKESLICE { if n.Op() == ir.OMAKESLICE {
n := n.(*ir.MakeExpr) n := n.(*ir.MakeExpr)
r := n.Cap
if r == nil { r := &n.Cap
r = n.Len if n.Cap == nil {
r = &n.Len
} }
if !ir.IsSmallIntConst(r) {
// Try to determine static values of make() calls, to avoid allocating them on the heap.
// We are doing this in escape analysis, so that it happens after inlining and devirtualization.
if s := ir.StaticValue(*r); s.Op() == ir.OLITERAL {
lit, ok := s.(*ir.BasicLit)
if !ok || lit.Val().Kind() != constant.Int {
base.Fatalf("unexpected BasicLit Kind")
}
if constant.Compare(lit.Val(), token.GEQ, constant.MakeInt64(0)) {
*r = lit
}
}
if !ir.IsSmallIntConst(*r) {
return "non-constant size" return "non-constant size"
} }
if t := n.Type(); t.Elem().Size() != 0 && ir.Int64Val(r) > ir.MaxImplicitStackVarSize/t.Elem().Size() { if t := n.Type(); t.Elem().Size() != 0 && ir.Int64Val(*r) > ir.MaxImplicitStackVarSize/t.Elem().Size() {
return "too large for stack" return "too large for stack"
} }
} }

View File

@ -123,7 +123,7 @@ func doesMakeSlice(x *string, y *string) { // ERROR "leaking param: x" "leaking
func nonconstArray() { func nonconstArray() {
n := 32 n := 32
s1 := make([]int, n) // ERROR "make\(\[\]int, n\) escapes to heap" s1 := make([]int, n) // ERROR "make\(\[\]int, 32\) does not escape"
s2 := make([]int, 0, n) // ERROR "make\(\[\]int, 0, n\) escapes to heap" s2 := make([]int, 0, n) // ERROR "make\(\[\]int, 0, 32\) does not escape"
_, _ = s1, s2 _, _ = s1, s2
} }

View File

@ -0,0 +1,108 @@
// errorcheck -0 -m
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package escape
const globalConstSize = 128
var globalVarSize = 128
//go:noinline
func testSlices() {
{
size := 128
_ = make([]byte, size) // ERROR "make\(\[\]byte, 128\) does not escape"
}
{
s := 128
size := s
_ = make([]byte, size) // ERROR "make\(\[\]byte, 128\) does not escape"
}
{
size := 128
_ = make([]byte, size) // ERROR "make\(\[\]byte, 128\) does not escape"
}
{
s := 128
size := s
_ = make([]byte, size) // ERROR "make\(\[\]byte, 128\) does not escape"
}
{
s1 := 128
s2 := 256
_ = make([]byte, s2, s1) // ERROR "make\(\[\]byte, s2, 128\) does not escape"
}
allocLen(256) // ERROR "make\(\[\]byte, 256\) does not escape" "inlining call"
allocCap(256) // ERROR "make\(\[\]byte, 0, 256\) does not escape" "inlining call"
_ = newT(256) // ERROR "make\(\[\]byte, 256\) does not escape" "inlining call"
{
size := globalConstSize
_ = make([]byte, size) // ERROR "make\(\[\]byte, 128\) does not escape"
}
allocLen(globalConstSize) // ERROR "make\(\[\]byte, 128\) does not escape" "inlining call"
allocCap(globalConstSize) // ERROR "make\(\[\]byte, 0, 128\) does not escape" "inlining call"
_ = newT(globalConstSize) // ERROR "make\(\[\]byte, 128\) does not escape" "inlining call"
{
c := 128
s := 256
_ = make([]byte, s, c) // ERROR "make\(\[\]byte, s, 128\) does not escape"
}
{
s := 256
_ = make([]byte, s, globalConstSize) // ERROR "make\(\[\]byte, s, 128\) does not escape"
}
{
_ = make([]byte, globalVarSize) // ERROR "make\(\[\]byte, globalVarSize\) escapes to heap"
_ = make([]byte, globalVarSize, globalConstSize) // ERROR "make\(\[\]byte, globalVarSize, 128\) does not escape"
}
}
func allocLen(l int) []byte { // ERROR "can inline"
return make([]byte, l) // ERROR "escapes to heap"
}
func allocCap(l int) []byte { // ERROR "can inline"
return make([]byte, 0, l) // ERROR "escapes to heap"
}
type t struct {
s []byte
}
func newT(l int) t { // ERROR "can inline"
return t{make([]byte, l)} // ERROR "make.*escapes to heap"
}
//go:noinline
func testMaps() {
size := 128
_ = make(map[string]int, size) // ERROR "does not escape"
_ = allocMapLen(128) // ERROR "does not escape" "inlining call"
_ = newM(128) // ERROR "does not escape" "inlining call"
}
func allocMapLen(l int) map[string]int { // ERROR "can inline"
return make(map[string]int, l) // ERROR "escapes to heap"
}
type m struct {
m map[string]int
}
func newM(l int) m { // ERROR "can inline"
return m{make(map[string]int, l)} // ERROR "make.*escapes to heap"
}

View File

@ -12,6 +12,6 @@ func f() { // ERROR ""
_ = make([]byte, 100, 1<<17) // ERROR "too large for stack" "" _ = make([]byte, 100, 1<<17) // ERROR "too large for stack" ""
_ = make([]byte, n, 1<<17) // ERROR "too large for stack" "" _ = make([]byte, n, 1<<17) // ERROR "too large for stack" ""
_ = make([]byte, n) // ERROR "non-constant size" "" _ = make([]byte, n) // ERROR "does not escape"
_ = make([]byte, 100, m) // ERROR "non-constant size" "" _ = make([]byte, 100, m) // ERROR "does not escape"
} }