]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: determine static values of len and cap in make() calls
authorMateusz Poliwczak <mpoliwczak34@gmail.com>
Mon, 17 Feb 2025 11:29:04 +0000 (11:29 +0000)
committerGopher Robot <gobot@golang.org>
Wed, 19 Feb 2025 21:38:58 +0000 (13:38 -0800)
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>
src/cmd/compile/internal/escape/utils.go
test/escape_array.go
test/escape_make_non_const.go [new file with mode: 0644]
test/fixedbugs/issue41635.go

index bd1d2c22a2cfb8b85bc70f7be4e2ee0c6e9b089b..d9cb9bdf8ec1bc036e3bb11bf1fe414c8e97b1b2 100644 (file)
@@ -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
+               }
+
+               // 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"
                }
        }
index 83062c9436e71d2a1ed9462633b2e8d829a63197..1a1eb5e4aafb6fbe9f1dd8c0f2e89aa9b263e351 100644 (file)
@@ -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 (file)
index 0000000..b5f5cb2
--- /dev/null
@@ -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"
+}
index 35c0034cdd40e0824f479e2fcda079b3cf638093..ede8a4f2c9c64b357fe27ee6609ad0387c9ccf42 100644 (file)
@@ -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"
 }