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" }