Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions src/cmd/compile/internal/escape/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

// 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) {

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"
}
}
Expand Down
4 changes: 2 additions & 2 deletions test/escape_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
108 changes: 108 additions & 0 deletions test/escape_make_non_const.go
Original file line number Diff line number Diff line change
@@ -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"
}
4 changes: 2 additions & 2 deletions test/fixedbugs/issue41635.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}