From ba3f568988bff69b3d00cd6ca76ab536eacbea48 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Mon, 17 Feb 2025 11:29:04 +0000 Subject: [PATCH] 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 Reviewed-by: Michael Knyszek Reviewed-by: Keith Randall Auto-Submit: Keith Randall Reviewed-by: Keith Randall --- src/cmd/compile/internal/escape/utils.go | 27 ++++-- test/escape_array.go | 4 +- test/escape_make_non_const.go | 108 +++++++++++++++++++++++ test/fixedbugs/issue41635.go | 4 +- 4 files changed, 134 insertions(+), 9 deletions(-) create mode 100644 test/escape_make_non_const.go diff --git a/src/cmd/compile/internal/escape/utils.go b/src/cmd/compile/internal/escape/utils.go index bd1d2c22a2..d9cb9bdf8e 100644 --- a/src/cmd/compile/internal/escape/utils.go +++ b/src/cmd/compile/internal/escape/utils.go @@ -5,9 +5,12 @@ package escape import ( + "cmd/compile/internal/base" "cmd/compile/internal/ir" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" + "go/constant" + "go/token" ) func isSliceSelfAssign(dst, src ir.Node) bool { @@ -206,14 +209,28 @@ func HeapAllocReason(n ir.Node) string { if n.Op() == ir.OMAKESLICE { n := n.(*ir.MakeExpr) - r := n.Cap - if r == nil { - r = n.Len + + r := &n.Cap + 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" } - 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" } } diff --git a/test/escape_array.go b/test/escape_array.go index 83062c9436..1a1eb5e4aa 100644 --- a/test/escape_array.go +++ b/test/escape_array.go @@ -123,7 +123,7 @@ func doesMakeSlice(x *string, y *string) { // ERROR "leaking param: x" "leaking func nonconstArray() { n := 32 - s1 := make([]int, n) // ERROR "make\(\[\]int, n\) escapes to heap" - s2 := make([]int, 0, n) // ERROR "make\(\[\]int, 0, n\) escapes to heap" + s1 := make([]int, n) // ERROR "make\(\[\]int, 32\) does not escape" + s2 := make([]int, 0, n) // ERROR "make\(\[\]int, 0, 32\) does not escape" _, _ = s1, s2 } diff --git a/test/escape_make_non_const.go b/test/escape_make_non_const.go new file mode 100644 index 0000000000..b5f5cb2e71 --- /dev/null +++ b/test/escape_make_non_const.go @@ -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" +} diff --git a/test/fixedbugs/issue41635.go b/test/fixedbugs/issue41635.go index 35c0034cdd..ede8a4f2c9 100644 --- a/test/fixedbugs/issue41635.go +++ b/test/fixedbugs/issue41635.go @@ -12,6 +12,6 @@ func f() { // ERROR "" _ = make([]byte, 100, 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, 100, m) // ERROR "non-constant size" "" + _ = make([]byte, n) // ERROR "does not escape" + _ = make([]byte, 100, m) // ERROR "does not escape" }