Skip to content

Commit ba3f568

Browse files
mateusz834gopherbot
authored andcommitted
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: d78c1b4 GitHub-Pull-Request: #71693 Reviewed-on: https://go-review.googlesource.com/c/go/+/649035 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> Reviewed-by: Keith Randall <[email protected]> Auto-Submit: Keith Randall <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent 28d7eec commit ba3f568

File tree

4 files changed

+134
-9
lines changed

4 files changed

+134
-9
lines changed

src/cmd/compile/internal/escape/utils.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
package escape
66

77
import (
8+
"cmd/compile/internal/base"
89
"cmd/compile/internal/ir"
910
"cmd/compile/internal/typecheck"
1011
"cmd/compile/internal/types"
12+
"go/constant"
13+
"go/token"
1114
)
1215

1316
func isSliceSelfAssign(dst, src ir.Node) bool {
@@ -206,14 +209,28 @@ func HeapAllocReason(n ir.Node) string {
206209

207210
if n.Op() == ir.OMAKESLICE {
208211
n := n.(*ir.MakeExpr)
209-
r := n.Cap
210-
if r == nil {
211-
r = n.Len
212+
213+
r := &n.Cap
214+
if n.Cap == nil {
215+
r = &n.Len
216+
}
217+
218+
// Try to determine static values of make() calls, to avoid allocating them on the heap.
219+
// We are doing this in escape analysis, so that it happens after inlining and devirtualization.
220+
if s := ir.StaticValue(*r); s.Op() == ir.OLITERAL {
221+
lit, ok := s.(*ir.BasicLit)
222+
if !ok || lit.Val().Kind() != constant.Int {
223+
base.Fatalf("unexpected BasicLit Kind")
224+
}
225+
if constant.Compare(lit.Val(), token.GEQ, constant.MakeInt64(0)) {
226+
*r = lit
227+
}
212228
}
213-
if !ir.IsSmallIntConst(r) {
229+
230+
if !ir.IsSmallIntConst(*r) {
214231
return "non-constant size"
215232
}
216-
if t := n.Type(); t.Elem().Size() != 0 && ir.Int64Val(r) > ir.MaxImplicitStackVarSize/t.Elem().Size() {
233+
if t := n.Type(); t.Elem().Size() != 0 && ir.Int64Val(*r) > ir.MaxImplicitStackVarSize/t.Elem().Size() {
217234
return "too large for stack"
218235
}
219236
}

test/escape_array.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func doesMakeSlice(x *string, y *string) { // ERROR "leaking param: x" "leaking
123123

124124
func nonconstArray() {
125125
n := 32
126-
s1 := make([]int, n) // ERROR "make\(\[\]int, n\) escapes to heap"
127-
s2 := make([]int, 0, n) // ERROR "make\(\[\]int, 0, n\) escapes to heap"
126+
s1 := make([]int, n) // ERROR "make\(\[\]int, 32\) does not escape"
127+
s2 := make([]int, 0, n) // ERROR "make\(\[\]int, 0, 32\) does not escape"
128128
_, _ = s1, s2
129129
}

test/escape_make_non_const.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// errorcheck -0 -m
2+
3+
// Copyright 2025 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package escape
8+
9+
const globalConstSize = 128
10+
11+
var globalVarSize = 128
12+
13+
//go:noinline
14+
func testSlices() {
15+
{
16+
size := 128
17+
_ = make([]byte, size) // ERROR "make\(\[\]byte, 128\) does not escape"
18+
}
19+
20+
{
21+
s := 128
22+
size := s
23+
_ = make([]byte, size) // ERROR "make\(\[\]byte, 128\) does not escape"
24+
}
25+
26+
{
27+
size := 128
28+
_ = make([]byte, size) // ERROR "make\(\[\]byte, 128\) does not escape"
29+
}
30+
31+
{
32+
s := 128
33+
size := s
34+
_ = make([]byte, size) // ERROR "make\(\[\]byte, 128\) does not escape"
35+
}
36+
37+
{
38+
s1 := 128
39+
s2 := 256
40+
_ = make([]byte, s2, s1) // ERROR "make\(\[\]byte, s2, 128\) does not escape"
41+
}
42+
43+
allocLen(256) // ERROR "make\(\[\]byte, 256\) does not escape" "inlining call"
44+
allocCap(256) // ERROR "make\(\[\]byte, 0, 256\) does not escape" "inlining call"
45+
_ = newT(256) // ERROR "make\(\[\]byte, 256\) does not escape" "inlining call"
46+
47+
{
48+
size := globalConstSize
49+
_ = make([]byte, size) // ERROR "make\(\[\]byte, 128\) does not escape"
50+
}
51+
52+
allocLen(globalConstSize) // ERROR "make\(\[\]byte, 128\) does not escape" "inlining call"
53+
allocCap(globalConstSize) // ERROR "make\(\[\]byte, 0, 128\) does not escape" "inlining call"
54+
_ = newT(globalConstSize) // ERROR "make\(\[\]byte, 128\) does not escape" "inlining call"
55+
56+
{
57+
c := 128
58+
s := 256
59+
_ = make([]byte, s, c) // ERROR "make\(\[\]byte, s, 128\) does not escape"
60+
}
61+
62+
{
63+
s := 256
64+
_ = make([]byte, s, globalConstSize) // ERROR "make\(\[\]byte, s, 128\) does not escape"
65+
}
66+
67+
{
68+
_ = make([]byte, globalVarSize) // ERROR "make\(\[\]byte, globalVarSize\) escapes to heap"
69+
_ = make([]byte, globalVarSize, globalConstSize) // ERROR "make\(\[\]byte, globalVarSize, 128\) does not escape"
70+
}
71+
}
72+
73+
func allocLen(l int) []byte { // ERROR "can inline"
74+
return make([]byte, l) // ERROR "escapes to heap"
75+
}
76+
77+
func allocCap(l int) []byte { // ERROR "can inline"
78+
return make([]byte, 0, l) // ERROR "escapes to heap"
79+
}
80+
81+
type t struct {
82+
s []byte
83+
}
84+
85+
func newT(l int) t { // ERROR "can inline"
86+
return t{make([]byte, l)} // ERROR "make.*escapes to heap"
87+
}
88+
89+
//go:noinline
90+
func testMaps() {
91+
size := 128
92+
_ = make(map[string]int, size) // ERROR "does not escape"
93+
94+
_ = allocMapLen(128) // ERROR "does not escape" "inlining call"
95+
_ = newM(128) // ERROR "does not escape" "inlining call"
96+
}
97+
98+
func allocMapLen(l int) map[string]int { // ERROR "can inline"
99+
return make(map[string]int, l) // ERROR "escapes to heap"
100+
}
101+
102+
type m struct {
103+
m map[string]int
104+
}
105+
106+
func newM(l int) m { // ERROR "can inline"
107+
return m{make(map[string]int, l)} // ERROR "make.*escapes to heap"
108+
}

test/fixedbugs/issue41635.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ func f() { // ERROR ""
1212
_ = make([]byte, 100, 1<<17) // ERROR "too large for stack" ""
1313
_ = make([]byte, n, 1<<17) // ERROR "too large for stack" ""
1414

15-
_ = make([]byte, n) // ERROR "non-constant size" ""
16-
_ = make([]byte, 100, m) // ERROR "non-constant size" ""
15+
_ = make([]byte, n) // ERROR "does not escape"
16+
_ = make([]byte, 100, m) // ERROR "does not escape"
1717
}

0 commit comments

Comments
 (0)