+
+
+
+
+`
diff --git a/src/crypto/tls/handshake_test.go b/src/crypto/tls/handshake_test.go
index ea8ac6fc837..13f1bb1dd50 100644
--- a/src/crypto/tls/handshake_test.go
+++ b/src/crypto/tls/handshake_test.go
@@ -47,6 +47,7 @@ var (
bogoMode = flag.Bool("bogo-mode", false, "Enabled bogo shim mode, ignore everything else")
bogoFilter = flag.String("bogo-filter", "", "BoGo test filter")
bogoLocalDir = flag.String("bogo-local-dir", "", "Local BoGo to use, instead of fetching from source")
+ bogoReport = flag.String("bogo-html-report", "", "File path to render an HTML report with BoGo results")
)
func runTestAndUpdateIfNeeded(t *testing.T, name string, run func(t *testing.T, update bool), wait bool) {
From a7917eed70c63840b2c59748c52ef9ff11fecf38 Mon Sep 17 00:00:00 2001
From: Michael Matloob
Date: Mon, 17 Mar 2025 11:45:52 -0400
Subject: [PATCH 002/421] internal/buildcfg: enable specializedmalloc
experiment
Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64_c2s16-perf_vs_parent,gotip-linux-amd64_c3h88-perf_vs_parent,gotip-linux-arm64_c4ah72-perf_vs_parent,gotip-linux-arm64_c4as16-perf_vs_parent
Change-Id: I6a6a6964c4c596bbf4f072b5a44a34c3ce4f6541
Reviewed-on: https://go-review.googlesource.com/c/go/+/696536
Reviewed-by: Michael Matloob
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Michael Knyszek
---
src/internal/buildcfg/exp.go | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/internal/buildcfg/exp.go b/src/internal/buildcfg/exp.go
index 707776b374b..d06913d9a7f 100644
--- a/src/internal/buildcfg/exp.go
+++ b/src/internal/buildcfg/exp.go
@@ -79,10 +79,11 @@ func ParseGOEXPERIMENT(goos, goarch, goexp string) (*ExperimentFlags, error) {
dwarf5Supported := (goos != "darwin" && goos != "ios" && goos != "aix")
baseline := goexperiment.Flags{
- RegabiWrappers: regabiSupported,
- RegabiArgs: regabiSupported,
- Dwarf5: dwarf5Supported,
- RandomizedHeapBase64: true,
+ RegabiWrappers: regabiSupported,
+ RegabiArgs: regabiSupported,
+ Dwarf5: dwarf5Supported,
+ RandomizedHeapBase64: true,
+ SizeSpecializedMalloc: true,
}
// Start with the statically enabled set of experiments.
From c54dc1418b6fbff4176aaaffcc9fab6f1ad631a6 Mon Sep 17 00:00:00 2001
From: matloob
Date: Wed, 1 Oct 2025 12:06:14 -0400
Subject: [PATCH 003/421] runtime: support valgrind (but not asan) in
specialized malloc functions
We're adding this so that the compiler doesn't need to know about
valgrind since it's just implemented using a build tag.
Change-Id: I6a6a696452b0379caceca2ae4e49195016f7a90d
Reviewed-on: https://go-review.googlesource.com/c/go/+/708296
Reviewed-by: Michael Matloob
LUCI-TryBot-Result: Go LUCI
Auto-Submit: Michael Matloob
Reviewed-by: Michael Knyszek
---
src/runtime/malloc_generated.go | 324 ++++++++++++++++++++++++++++++++
src/runtime/malloc_stubs.go | 9 +
2 files changed, 333 insertions(+)
diff --git a/src/runtime/malloc_generated.go b/src/runtime/malloc_generated.go
index 600048c6755..2215dbaddb2 100644
--- a/src/runtime/malloc_generated.go
+++ b/src/runtime/malloc_generated.go
@@ -150,6 +150,10 @@ func mallocgcSmallScanNoHeaderSC1(size uintptr, typ *_type, needzero bool) unsaf
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -304,6 +308,10 @@ func mallocgcSmallScanNoHeaderSC2(size uintptr, typ *_type, needzero bool) unsaf
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -458,6 +466,10 @@ func mallocgcSmallScanNoHeaderSC3(size uintptr, typ *_type, needzero bool) unsaf
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -612,6 +624,10 @@ func mallocgcSmallScanNoHeaderSC4(size uintptr, typ *_type, needzero bool) unsaf
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -766,6 +782,10 @@ func mallocgcSmallScanNoHeaderSC5(size uintptr, typ *_type, needzero bool) unsaf
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -920,6 +940,10 @@ func mallocgcSmallScanNoHeaderSC6(size uintptr, typ *_type, needzero bool) unsaf
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -1074,6 +1098,10 @@ func mallocgcSmallScanNoHeaderSC7(size uintptr, typ *_type, needzero bool) unsaf
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -1228,6 +1256,10 @@ func mallocgcSmallScanNoHeaderSC8(size uintptr, typ *_type, needzero bool) unsaf
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -1382,6 +1414,10 @@ func mallocgcSmallScanNoHeaderSC9(size uintptr, typ *_type, needzero bool) unsaf
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -1536,6 +1572,10 @@ func mallocgcSmallScanNoHeaderSC10(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -1690,6 +1730,10 @@ func mallocgcSmallScanNoHeaderSC11(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -1844,6 +1888,10 @@ func mallocgcSmallScanNoHeaderSC12(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -1998,6 +2046,10 @@ func mallocgcSmallScanNoHeaderSC13(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -2152,6 +2204,10 @@ func mallocgcSmallScanNoHeaderSC14(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -2306,6 +2362,10 @@ func mallocgcSmallScanNoHeaderSC15(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -2460,6 +2520,10 @@ func mallocgcSmallScanNoHeaderSC16(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -2614,6 +2678,10 @@ func mallocgcSmallScanNoHeaderSC17(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -2768,6 +2836,10 @@ func mallocgcSmallScanNoHeaderSC18(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -2922,6 +2994,10 @@ func mallocgcSmallScanNoHeaderSC19(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -3076,6 +3152,10 @@ func mallocgcSmallScanNoHeaderSC20(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -3230,6 +3310,10 @@ func mallocgcSmallScanNoHeaderSC21(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -3384,6 +3468,10 @@ func mallocgcSmallScanNoHeaderSC22(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -3538,6 +3626,10 @@ func mallocgcSmallScanNoHeaderSC23(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -3692,6 +3784,10 @@ func mallocgcSmallScanNoHeaderSC24(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -3846,6 +3942,10 @@ func mallocgcSmallScanNoHeaderSC25(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -4000,6 +4100,10 @@ func mallocgcSmallScanNoHeaderSC26(size uintptr, typ *_type, needzero bool) unsa
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -4064,6 +4168,10 @@ func mallocTiny1(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
const elemsize = 0
{
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -4142,6 +4250,10 @@ func mallocTiny1(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
x = add(x, elemsize-constsize)
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -4206,6 +4318,10 @@ func mallocTiny2(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
const elemsize = 0
{
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -4284,6 +4400,10 @@ func mallocTiny2(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
x = add(x, elemsize-constsize)
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -4348,6 +4468,10 @@ func mallocTiny3(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
const elemsize = 0
{
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -4426,6 +4550,10 @@ func mallocTiny3(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
x = add(x, elemsize-constsize)
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -4490,6 +4618,10 @@ func mallocTiny4(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
const elemsize = 0
{
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -4568,6 +4700,10 @@ func mallocTiny4(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
x = add(x, elemsize-constsize)
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -4632,6 +4768,10 @@ func mallocTiny5(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
const elemsize = 0
{
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -4710,6 +4850,10 @@ func mallocTiny5(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
x = add(x, elemsize-constsize)
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -4774,6 +4918,10 @@ func mallocTiny6(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
const elemsize = 0
{
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -4852,6 +5000,10 @@ func mallocTiny6(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
x = add(x, elemsize-constsize)
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -4916,6 +5068,10 @@ func mallocTiny7(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
const elemsize = 0
{
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -4994,6 +5150,10 @@ func mallocTiny7(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
x = add(x, elemsize-constsize)
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -5058,6 +5218,10 @@ func mallocTiny8(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
const elemsize = 0
{
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -5136,6 +5300,10 @@ func mallocTiny8(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
x = add(x, elemsize-constsize)
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -5200,6 +5368,10 @@ func mallocTiny9(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
const elemsize = 0
{
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -5278,6 +5450,10 @@ func mallocTiny9(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
x = add(x, elemsize-constsize)
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -5342,6 +5518,10 @@ func mallocTiny10(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
const elemsize = 0
{
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -5420,6 +5600,10 @@ func mallocTiny10(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
x = add(x, elemsize-constsize)
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -5484,6 +5668,10 @@ func mallocTiny11(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
const elemsize = 0
{
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -5562,6 +5750,10 @@ func mallocTiny11(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
x = add(x, elemsize-constsize)
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -5626,6 +5818,10 @@ func mallocTiny12(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
const elemsize = 0
{
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -5704,6 +5900,10 @@ func mallocTiny12(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
x = add(x, elemsize-constsize)
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -5768,6 +5968,10 @@ func mallocTiny13(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
const elemsize = 0
{
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -5846,6 +6050,10 @@ func mallocTiny13(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
x = add(x, elemsize-constsize)
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -5910,6 +6118,10 @@ func mallocTiny14(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
const elemsize = 0
{
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -5988,6 +6200,10 @@ func mallocTiny14(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
x = add(x, elemsize-constsize)
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -6052,6 +6268,10 @@ func mallocTiny15(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
const elemsize = 0
{
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -6130,6 +6350,10 @@ func mallocTiny15(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
x = add(x, elemsize-constsize)
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -6223,6 +6447,10 @@ func mallocgcSmallNoScanSC2(size uintptr, typ *_type, needzero bool) unsafe.Poin
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -6316,6 +6544,10 @@ func mallocgcSmallNoScanSC3(size uintptr, typ *_type, needzero bool) unsafe.Poin
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -6409,6 +6641,10 @@ func mallocgcSmallNoScanSC4(size uintptr, typ *_type, needzero bool) unsafe.Poin
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -6502,6 +6738,10 @@ func mallocgcSmallNoScanSC5(size uintptr, typ *_type, needzero bool) unsafe.Poin
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -6595,6 +6835,10 @@ func mallocgcSmallNoScanSC6(size uintptr, typ *_type, needzero bool) unsafe.Poin
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -6688,6 +6932,10 @@ func mallocgcSmallNoScanSC7(size uintptr, typ *_type, needzero bool) unsafe.Poin
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -6781,6 +7029,10 @@ func mallocgcSmallNoScanSC8(size uintptr, typ *_type, needzero bool) unsafe.Poin
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -6874,6 +7126,10 @@ func mallocgcSmallNoScanSC9(size uintptr, typ *_type, needzero bool) unsafe.Poin
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -6967,6 +7223,10 @@ func mallocgcSmallNoScanSC10(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -7060,6 +7320,10 @@ func mallocgcSmallNoScanSC11(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -7153,6 +7417,10 @@ func mallocgcSmallNoScanSC12(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -7246,6 +7514,10 @@ func mallocgcSmallNoScanSC13(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -7339,6 +7611,10 @@ func mallocgcSmallNoScanSC14(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -7432,6 +7708,10 @@ func mallocgcSmallNoScanSC15(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -7525,6 +7805,10 @@ func mallocgcSmallNoScanSC16(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -7618,6 +7902,10 @@ func mallocgcSmallNoScanSC17(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -7711,6 +7999,10 @@ func mallocgcSmallNoScanSC18(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -7804,6 +8096,10 @@ func mallocgcSmallNoScanSC19(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -7897,6 +8193,10 @@ func mallocgcSmallNoScanSC20(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -7990,6 +8290,10 @@ func mallocgcSmallNoScanSC21(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -8083,6 +8387,10 @@ func mallocgcSmallNoScanSC22(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -8176,6 +8484,10 @@ func mallocgcSmallNoScanSC23(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -8269,6 +8581,10 @@ func mallocgcSmallNoScanSC24(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -8362,6 +8678,10 @@ func mallocgcSmallNoScanSC25(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
@@ -8455,6 +8775,10 @@ func mallocgcSmallNoScanSC26(size uintptr, typ *_type, needzero bool) unsafe.Poi
gcStart(t)
}
}
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
assistG.gcAssistBytes -= int64(elemsize - size)
diff --git a/src/runtime/malloc_stubs.go b/src/runtime/malloc_stubs.go
index 7fd14441893..224746f3d41 100644
--- a/src/runtime/malloc_stubs.go
+++ b/src/runtime/malloc_stubs.go
@@ -50,6 +50,8 @@ func mallocPanic(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
panic("not defined for sizeclass")
}
+// WARNING: mallocStub does not do any work for sanitizers so callers need
+// to steer out of this codepath early if sanitizers are enabled.
func mallocStub(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
if doubleCheckMalloc {
if gcphase == _GCmarktermination {
@@ -77,6 +79,13 @@ func mallocStub(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
// Actually do the allocation.
x, elemsize := inlinedMalloc(size, typ, needzero)
+ // Notify valgrind, if enabled.
+ // To allow the compiler to not know about valgrind, we do valgrind instrumentation
+ // unlike the other sanitizers.
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
// Adjust our GC assist debt to account for internal fragmentation.
if gcBlackenEnabled != 0 && elemsize != 0 {
if assistG := getg().m.curg; assistG != nil {
From ebb72bef44a0e125c7f900a04af6538e3c39dfc6 Mon Sep 17 00:00:00 2001
From: Cherry Mui
Date: Fri, 20 Jun 2025 12:02:18 -0400
Subject: [PATCH 004/421] cmd/compile: don't treat devel compiler as a released
compiler
The compiler has a logic to print different messages on internal
compiler error depending on whether this is a released version of
Go. It hides the panic stack trace if it is a released version. It
does this by checking the version and see if it has a "go" prefix.
This includes all the released versions. However, for a non-
released build, if there is no explicit version set, cmd/dist now
sets the toolchain version as go1.X-devel_XXX, which makes it be
treated as a released compiler, and causes the stack trace to be
hidden. Change the logic to not match a devel compiler as a
released compiler.
Cherry-picked from the dev.simd branch. This CL is not
necessarily SIMD specific. Apply early to reduce risk.
Change-Id: I5d3b2101527212f825b6e4000b36030c4f83870b
Reviewed-on: https://go-review.googlesource.com/c/go/+/682975
Reviewed-by: David Chase
LUCI-TryBot-Result: Go LUCI
Reviewed-on: https://go-review.googlesource.com/c/go/+/708855
Reviewed-by: Junyang Shao
---
src/cmd/compile/internal/base/print.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/cmd/compile/internal/base/print.go b/src/cmd/compile/internal/base/print.go
index 119f06fbc03..9e3348c1ecc 100644
--- a/src/cmd/compile/internal/base/print.go
+++ b/src/cmd/compile/internal/base/print.go
@@ -220,7 +220,7 @@ func FatalfAt(pos src.XPos, format string, args ...interface{}) {
fmt.Printf("\n")
// If this is a released compiler version, ask for a bug report.
- if Debug.Panic == 0 && strings.HasPrefix(buildcfg.Version, "go") {
+ if Debug.Panic == 0 && strings.HasPrefix(buildcfg.Version, "go") && !strings.Contains(buildcfg.Version, "devel") {
fmt.Printf("\n")
fmt.Printf("Please file a bug report including a short program that triggers the error.\n")
fmt.Printf("https://go.dev/issue/new\n")
From ab043953cbd6e3cd262548710f35f05924aa8f32 Mon Sep 17 00:00:00 2001
From: David Chase
Date: Wed, 2 Jul 2025 18:00:12 -0400
Subject: [PATCH 005/421] cmd/compile: minor tweak for race detector
This makes the front-end a little bit less temp-happy
when instrumenting, which repairs the "is it a constant?"
test in the simd intrinsic conversion which is otherwise
broken by race detection.
Also, this will perhaps be better code.
Cherry-picked from the dev.simd branch. This CL is not
necessarily SIMD specific. Apply early to reduce risk.
Change-Id: I84b7a45b7bff62bb2c9f9662466b50858d288645
Reviewed-on: https://go-review.googlesource.com/c/go/+/685637
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Junyang Shao
Reviewed-by: Cherry Mui
Reviewed-on: https://go-review.googlesource.com/c/go/+/708856
---
src/cmd/compile/internal/walk/walk.go | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/src/cmd/compile/internal/walk/walk.go b/src/cmd/compile/internal/walk/walk.go
index a7d8182a747..25add3d8043 100644
--- a/src/cmd/compile/internal/walk/walk.go
+++ b/src/cmd/compile/internal/walk/walk.go
@@ -275,6 +275,15 @@ func backingArrayPtrLen(n ir.Node) (ptr, length ir.Node) {
// function calls, which could clobber function call arguments/results
// currently on the stack.
func mayCall(n ir.Node) bool {
+ // This is intended to avoid putting constants
+ // into temporaries with the race detector (or other
+ // instrumentation) which interferes with simple
+ // "this is a constant" tests in ssagen.
+ // Also, it will generally lead to better code.
+ if n.Op() == ir.OLITERAL {
+ return false
+ }
+
// When instrumenting, any expression might require function calls.
if base.Flag.Cfg.Instrumenting {
return true
From 10e796884905d23ab2419cc158769e8fdc73de4e Mon Sep 17 00:00:00 2001
From: Junyang Shao
Date: Tue, 12 Aug 2025 16:53:44 +0000
Subject: [PATCH 006/421] cmd/compile: accounts rematerialize ops's output
reginfo
This CL implements the check for rematerializeable value's output
regspec at its remateralization site. It has some potential problems,
please see the TODO in regalloc.go.
Fixes #70451.
Cherry-picked from the dev.simd branch. This CL is not
necessarily SIMD specific. Apply early to reduce risk.
Change-Id: Ib624b967031776851136554719e939e9bf116b7c
Reviewed-on: https://go-review.googlesource.com/c/go/+/695315
Reviewed-by: David Chase
TryBot-Bypass: David Chase
Reviewed-on: https://go-review.googlesource.com/c/go/+/708857
Reviewed-by: Junyang Shao
LUCI-TryBot-Result: Go LUCI
---
src/cmd/compile/internal/ssa/func.go | 1 +
src/cmd/compile/internal/ssa/func_test.go | 5 ++++
src/cmd/compile/internal/ssa/regalloc.go | 23 +++++++++++++++++
src/cmd/compile/internal/ssa/regalloc_test.go | 25 +++++++++++++++++++
4 files changed, 54 insertions(+)
diff --git a/src/cmd/compile/internal/ssa/func.go b/src/cmd/compile/internal/ssa/func.go
index 5736f0b8126..fc8cb3f2fef 100644
--- a/src/cmd/compile/internal/ssa/func.go
+++ b/src/cmd/compile/internal/ssa/func.go
@@ -102,6 +102,7 @@ func (c *Config) NewFunc(fe Frontend, cache *Cache) *Func {
NamedValues: make(map[LocalSlot][]*Value),
CanonicalLocalSlots: make(map[LocalSlot]*LocalSlot),
CanonicalLocalSplits: make(map[LocalSlotSplitKey]*LocalSlot),
+ OwnAux: &AuxCall{},
}
}
diff --git a/src/cmd/compile/internal/ssa/func_test.go b/src/cmd/compile/internal/ssa/func_test.go
index a1e639e0486..4639d674e14 100644
--- a/src/cmd/compile/internal/ssa/func_test.go
+++ b/src/cmd/compile/internal/ssa/func_test.go
@@ -250,6 +250,11 @@ func Exit(arg string) ctrl {
return ctrl{BlockExit, arg, []string{}}
}
+// Ret specifies a BlockRet.
+func Ret(arg string) ctrl {
+ return ctrl{BlockRet, arg, []string{}}
+}
+
// Eq specifies a BlockAMD64EQ.
func Eq(cond, sub, alt string) ctrl {
return ctrl{BlockAMD64EQ, cond, []string{sub, alt}}
diff --git a/src/cmd/compile/internal/ssa/regalloc.go b/src/cmd/compile/internal/ssa/regalloc.go
index 1ce85a8f63b..56f9e550b2d 100644
--- a/src/cmd/compile/internal/ssa/regalloc.go
+++ b/src/cmd/compile/internal/ssa/regalloc.go
@@ -609,6 +609,29 @@ func (s *regAllocState) allocValToReg(v *Value, mask regMask, nospill bool, pos
} else if v.rematerializeable() {
// Rematerialize instead of loading from the spill location.
c = v.copyIntoWithXPos(s.curBlock, pos)
+ // We need to consider its output mask and potentially issue a Copy
+ // if there are register mask conflicts.
+ // This currently happens for the SIMD package only between GP and FP
+ // register. Because Intel's vector extension can put integer value into
+ // FP, which is seen as a vector. Example instruction: VPSLL[BWDQ]
+ // Because GP and FP masks do not overlap, mask & outputMask == 0
+ // detects this situation thoroughly.
+ sourceMask := s.regspec(c).outputs[0].regs
+ if mask&sourceMask == 0 && !onWasmStack {
+ s.setOrig(c, v)
+ s.assignReg(s.allocReg(sourceMask, v), v, c)
+ // v.Type for the new OpCopy is likely wrong and it might delay the problem
+ // until ssa to asm lowering, which might need the types to generate the right
+ // assembly for OpCopy. For Intel's GP to FP move, it happens to be that
+ // MOV instruction has such a variant so it happens to be right.
+ // But it's unclear for other architectures or situations, and the problem
+ // might be exposed when the assembler sees illegal instructions.
+ // Right now make we still pick v.Type, because at least its size should be correct
+ // for the rematerialization case the amd64 SIMD package exposed.
+ // TODO: We might need to figure out a way to find the correct type or make
+ // the asm lowering use reg info only for OpCopy.
+ c = s.curBlock.NewValue1(pos, OpCopy, v.Type, c)
+ }
} else {
// Load v from its spill location.
spill := s.makeSpill(v, s.curBlock)
diff --git a/src/cmd/compile/internal/ssa/regalloc_test.go b/src/cmd/compile/internal/ssa/regalloc_test.go
index 0f69b852d12..79f94da0114 100644
--- a/src/cmd/compile/internal/ssa/regalloc_test.go
+++ b/src/cmd/compile/internal/ssa/regalloc_test.go
@@ -6,6 +6,7 @@ package ssa
import (
"cmd/compile/internal/types"
+ "cmd/internal/obj/x86"
"fmt"
"testing"
)
@@ -279,3 +280,27 @@ func numOps(b *Block, op Op) int {
}
return n
}
+
+func TestRematerializeableRegCompatible(t *testing.T) {
+ c := testConfig(t)
+ f := c.Fun("entry",
+ Bloc("entry",
+ Valu("mem", OpInitMem, types.TypeMem, 0, nil),
+ Valu("x", OpAMD64MOVLconst, c.config.Types.Int32, 1, nil),
+ Valu("a", OpAMD64POR, c.config.Types.Float32, 0, nil, "x", "x"),
+ Valu("res", OpMakeResult, types.NewResults([]*types.Type{c.config.Types.Float32, types.TypeMem}), 0, nil, "a", "mem"),
+ Ret("res"),
+ ),
+ )
+ regalloc(f.f)
+ checkFunc(f.f)
+ moveFound := false
+ for _, v := range f.f.Blocks[0].Values {
+ if v.Op == OpCopy && x86.REG_X0 <= v.Reg() && v.Reg() <= x86.REG_X31 {
+ moveFound = true
+ }
+ }
+ if !moveFound {
+ t.Errorf("Expects an Copy to be issued, but got: %+v", f.f)
+ }
+}
From ec70d1902355f10e0ab4788334b80db11ab69785 Mon Sep 17 00:00:00 2001
From: David Chase
Date: Wed, 20 Aug 2025 12:29:02 -0400
Subject: [PATCH 007/421] cmd/compile: rewrite to elide Slicemask from len==c>0
slicing
This might have been something that prove could be educated
into figuring out, but this also works, and it also helps
prove downstream.
Adjusted the prove test, because this change moved a message.
Cherry-picked from the dev.simd branch. This CL is not
necessarily SIMD specific. Apply early to reduce risk.
Change-Id: I5eabe639eff5db9cd9766a6a8666fdb4973829cb
Reviewed-on: https://go-review.googlesource.com/c/go/+/697715
Commit-Queue: David Chase
Reviewed-by: Cherry Mui
TryBot-Bypass: David Chase
Reviewed-on: https://go-review.googlesource.com/c/go/+/708858
LUCI-TryBot-Result: Go LUCI
Reviewed-by: David Chase
Reviewed-by: Junyang Shao
---
.../compile/internal/ssa/_gen/generic.rules | 4 +
.../compile/internal/ssa/rewritegeneric.go | 87 +++++++++++++++++++
test/prove.go | 4 +-
3 files changed, 93 insertions(+), 2 deletions(-)
diff --git a/src/cmd/compile/internal/ssa/_gen/generic.rules b/src/cmd/compile/internal/ssa/_gen/generic.rules
index b16aa473cd5..6fdea7cc7a3 100644
--- a/src/cmd/compile/internal/ssa/_gen/generic.rules
+++ b/src/cmd/compile/internal/ssa/_gen/generic.rules
@@ -989,6 +989,10 @@
(Const64 [0])
(Const64 [0]))
+// Special rule to help constant slicing; len > 0 implies cap > 0 implies Slicemask is all 1
+(SliceMake (AddPtr x (And64 y (Slicemask _))) w:(Const64 [c]) z) && c > 0 => (SliceMake (AddPtr x y) w z)
+(SliceMake (AddPtr x (And32 y (Slicemask _))) w:(Const32 [c]) z) && c > 0 => (SliceMake (AddPtr x y) w z)
+
// interface ops
(ConstInterface) =>
(IMake
diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go
index 5e0135be3a5..5720063f34b 100644
--- a/src/cmd/compile/internal/ssa/rewritegeneric.go
+++ b/src/cmd/compile/internal/ssa/rewritegeneric.go
@@ -422,6 +422,8 @@ func rewriteValuegeneric(v *Value) bool {
return rewriteValuegeneric_OpSliceCap(v)
case OpSliceLen:
return rewriteValuegeneric_OpSliceLen(v)
+ case OpSliceMake:
+ return rewriteValuegeneric_OpSliceMake(v)
case OpSlicePtr:
return rewriteValuegeneric_OpSlicePtr(v)
case OpSlicemask:
@@ -30514,6 +30516,91 @@ func rewriteValuegeneric_OpSliceLen(v *Value) bool {
}
return false
}
+func rewriteValuegeneric_OpSliceMake(v *Value) bool {
+ v_2 := v.Args[2]
+ v_1 := v.Args[1]
+ v_0 := v.Args[0]
+ b := v.Block
+ // match: (SliceMake (AddPtr x (And64 y (Slicemask _))) w:(Const64 [c]) z)
+ // cond: c > 0
+ // result: (SliceMake (AddPtr x y) w z)
+ for {
+ if v_0.Op != OpAddPtr {
+ break
+ }
+ t := v_0.Type
+ _ = v_0.Args[1]
+ x := v_0.Args[0]
+ v_0_1 := v_0.Args[1]
+ if v_0_1.Op != OpAnd64 {
+ break
+ }
+ _ = v_0_1.Args[1]
+ v_0_1_0 := v_0_1.Args[0]
+ v_0_1_1 := v_0_1.Args[1]
+ for _i0 := 0; _i0 <= 1; _i0, v_0_1_0, v_0_1_1 = _i0+1, v_0_1_1, v_0_1_0 {
+ y := v_0_1_0
+ if v_0_1_1.Op != OpSlicemask {
+ continue
+ }
+ w := v_1
+ if w.Op != OpConst64 {
+ continue
+ }
+ c := auxIntToInt64(w.AuxInt)
+ z := v_2
+ if !(c > 0) {
+ continue
+ }
+ v.reset(OpSliceMake)
+ v0 := b.NewValue0(v.Pos, OpAddPtr, t)
+ v0.AddArg2(x, y)
+ v.AddArg3(v0, w, z)
+ return true
+ }
+ break
+ }
+ // match: (SliceMake (AddPtr x (And32 y (Slicemask _))) w:(Const32 [c]) z)
+ // cond: c > 0
+ // result: (SliceMake (AddPtr x y) w z)
+ for {
+ if v_0.Op != OpAddPtr {
+ break
+ }
+ t := v_0.Type
+ _ = v_0.Args[1]
+ x := v_0.Args[0]
+ v_0_1 := v_0.Args[1]
+ if v_0_1.Op != OpAnd32 {
+ break
+ }
+ _ = v_0_1.Args[1]
+ v_0_1_0 := v_0_1.Args[0]
+ v_0_1_1 := v_0_1.Args[1]
+ for _i0 := 0; _i0 <= 1; _i0, v_0_1_0, v_0_1_1 = _i0+1, v_0_1_1, v_0_1_0 {
+ y := v_0_1_0
+ if v_0_1_1.Op != OpSlicemask {
+ continue
+ }
+ w := v_1
+ if w.Op != OpConst32 {
+ continue
+ }
+ c := auxIntToInt32(w.AuxInt)
+ z := v_2
+ if !(c > 0) {
+ continue
+ }
+ v.reset(OpSliceMake)
+ v0 := b.NewValue0(v.Pos, OpAddPtr, t)
+ v0.AddArg2(x, y)
+ v.AddArg3(v0, w, z)
+ return true
+ }
+ break
+ }
+ return false
+}
func rewriteValuegeneric_OpSlicePtr(v *Value) bool {
v_0 := v.Args[0]
// match: (SlicePtr (SliceMake (SlicePtr x) _ _))
diff --git a/test/prove.go b/test/prove.go
index 70a27865cfd..6d2bb0962be 100644
--- a/test/prove.go
+++ b/test/prove.go
@@ -511,10 +511,10 @@ func f19() (e int64, err error) {
func sm1(b []int, x int) {
// Test constant argument to slicemask.
- useSlice(b[2:8]) // ERROR "Proved slicemask not needed$"
+ useSlice(b[2:8]) // optimized away earlier by rewrite
// Test non-constant argument with known limits.
if cap(b) > 10 {
- useSlice(b[2:])
+ useSlice(b[2:]) // ERROR "Proved slicemask not needed$"
}
}
From 1caa95acfa9d516eb3bc26292b5601bea25a4e79 Mon Sep 17 00:00:00 2001
From: David Chase
Date: Wed, 3 Sep 2025 13:09:32 -0400
Subject: [PATCH 008/421] cmd/compile: enhance prove to deal with double-offset
IsInBounds checks
For chunked iterations (useful for, but not exclusive to,
SIMD calculations) it is common to see the combination of
```
for ; i <= len(m)-4; i += 4 {
```
and
```
r0, r1, r2, r3 := m[i], m[i+1], m[i+2], m[i+3]
``
Prove did not handle the case of len-offset1 vs index+offset2
checking, but this change fixes this. There may be other
similar cases yet to handle -- this worked for the chunked
loops for simd, as well as a handful in std.
Cherry-picked from the dev.simd branch. This CL is not
necessarily SIMD specific. Apply early to reduce risk.
Change-Id: I3785df83028d517e5e5763206653b34b2befd3d0
Reviewed-on: https://go-review.googlesource.com/c/go/+/700696
Reviewed-by: Keith Randall
Reviewed-by: Keith Randall
LUCI-TryBot-Result: Go LUCI
Reviewed-on: https://go-review.googlesource.com/c/go/+/708859
Reviewed-by: David Chase
---
src/cmd/compile/internal/ssa/prove.go | 66 +++++++++++++++++++++++++++
test/prove.go | 12 ++---
2 files changed, 72 insertions(+), 6 deletions(-)
diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go
index 309229b4d75..7b860a6f9ea 100644
--- a/src/cmd/compile/internal/ssa/prove.go
+++ b/src/cmd/compile/internal/ssa/prove.go
@@ -2174,6 +2174,65 @@ func unsignedSubUnderflows(a, b uint64) bool {
return a < b
}
+// checkForChunkedIndexBounds looks for index expressions of the form
+// A[i+delta] where delta < K and i <= len(A)-K. That is, this is a chunked
+// iteration where the index is not directly compared to the length.
+func checkForChunkedIndexBounds(ft *factsTable, b *Block, index, bound *Value) bool {
+ if bound.Op != OpSliceLen {
+ return false
+ }
+ lim := ft.limits[index.ID]
+ if lim.min < 0 {
+ return false
+ }
+ i, delta := isConstDelta(index)
+ if i == nil {
+ return false
+ }
+ if delta < 0 {
+ return false
+ }
+ // special case for blocked iteration over a slice.
+ // slicelen > i + delta && <==== if clauses above
+ // && index >= 0 <==== if clause above
+ // delta >= 0 && <==== if clause above
+ // slicelen-K >/>= x <==== checked below
+ // && K >=/> delta <==== checked below
+ // then v > w
+ // example: i <=/< len - 4/3 means i+{0,1,2,3} are legal indices
+ for o := ft.orderings[i.ID]; o != nil; o = o.next {
+ if o.d != signed {
+ continue
+ }
+ if ow := o.w; ow.Op == OpAdd64 {
+ var lenOffset *Value
+ if ow.Args[0] == bound {
+ lenOffset = ow.Args[1]
+ } else if ow.Args[1] == bound {
+ lenOffset = ow.Args[0]
+ }
+ if lenOffset == nil || lenOffset.Op != OpConst64 {
+ continue
+ }
+ if K := -lenOffset.AuxInt; K >= 0 {
+ or := o.r
+ if or == lt {
+ or = lt | eq
+ K++
+ if K < 0 {
+ continue
+ }
+ }
+
+ if delta < K && or == lt|eq {
+ return true
+ }
+ }
+ }
+ }
+ return false
+}
+
func addLocalFacts(ft *factsTable, b *Block) {
// Propagate constant ranges among values in this block.
// We do this before the second loop so that we have the
@@ -2285,6 +2344,13 @@ func addLocalFacts(ft *factsTable, b *Block) {
if v.Args[0].Op == OpSliceMake {
ft.update(b, v, v.Args[0].Args[2], signed, eq)
}
+ case OpIsInBounds:
+ if checkForChunkedIndexBounds(ft, b, v.Args[0], v.Args[1]) {
+ if b.Func.pass.debug > 0 {
+ b.Func.Warnl(v.Pos, "Proved %s for blocked indexing", v.Op)
+ }
+ ft.booleanTrue(v)
+ }
case OpPhi:
addLocalFactsPhi(ft, v)
}
diff --git a/test/prove.go b/test/prove.go
index 6d2bb0962be..bcc023dfec6 100644
--- a/test/prove.go
+++ b/test/prove.go
@@ -773,8 +773,8 @@ func indexGT0(b []byte, n int) {
func unrollUpExcl(a []int) int {
var i, x int
for i = 0; i < len(a)-1; i += 2 { // ERROR "Induction variable: limits \[0,\?\), increment 2$"
- x += a[i] // ERROR "Proved IsInBounds$"
- x += a[i+1]
+ x += a[i] // ERROR "Proved IsInBounds$"
+ x += a[i+1] // ERROR "Proved IsInBounds( for blocked indexing)?$"
}
if i == len(a)-1 {
x += a[i]
@@ -786,8 +786,8 @@ func unrollUpExcl(a []int) int {
func unrollUpIncl(a []int) int {
var i, x int
for i = 0; i <= len(a)-2; i += 2 { // ERROR "Induction variable: limits \[0,\?\], increment 2$"
- x += a[i] // ERROR "Proved IsInBounds$"
- x += a[i+1]
+ x += a[i] // ERROR "Proved IsInBounds$"
+ x += a[i+1] // ERROR "Proved IsInBounds( for blocked indexing)?$"
}
if i == len(a)-1 {
x += a[i]
@@ -839,7 +839,7 @@ func unrollExclStepTooLarge(a []int) int {
var i, x int
for i = 0; i < len(a)-1; i += 3 {
x += a[i]
- x += a[i+1]
+ x += a[i+1] // ERROR "Proved IsInBounds( for blocked indexing)?$"
}
if i == len(a)-1 {
x += a[i]
@@ -852,7 +852,7 @@ func unrollInclStepTooLarge(a []int) int {
var i, x int
for i = 0; i <= len(a)-2; i += 3 {
x += a[i]
- x += a[i+1]
+ x += a[i+1] // ERROR "Proved IsInBounds( for blocked indexing)?$"
}
if i == len(a)-1 {
x += a[i]
From 18cd4a1fc7d5387ae91ffc23328e4fc81f93681d Mon Sep 17 00:00:00 2001
From: Cherry Mui
Date: Mon, 15 Sep 2025 21:27:19 -0400
Subject: [PATCH 009/421] cmd/compile: use the right type for spill slot
Currently, when shuffling registers, if we need to spill a
register, we always create a spill slot of type int64. The type
doesn't actually matter, as long as it is wide enough to hold the
registers. This is no longer true with SIMD registers, which could
be wider than a int64. Create the slot with the proper type
instead.
Cherry-picked from the dev.simd branch. This CL is not
necessarily SIMD specific. Apply early to reduce risk. Test is
SIMD specific so not included for now.
Change-Id: I85c82e2532001bfdefe98c9446f2dd18583d49b4
Reviewed-on: https://go-review.googlesource.com/c/go/+/704055
TryBot-Bypass: Cherry Mui
Reviewed-by: David Chase
Reviewed-by: Junyang Shao
Reviewed-on: https://go-review.googlesource.com/c/go/+/708860
LUCI-TryBot-Result: Go LUCI
---
src/cmd/compile/internal/ssa/regalloc.go | 5 +----
src/cmd/compile/internal/ssa/value.go | 2 +-
2 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/src/cmd/compile/internal/ssa/regalloc.go b/src/cmd/compile/internal/ssa/regalloc.go
index 56f9e550b2d..26c742d7fe7 100644
--- a/src/cmd/compile/internal/ssa/regalloc.go
+++ b/src/cmd/compile/internal/ssa/regalloc.go
@@ -2690,7 +2690,6 @@ func (e *edgeState) erase(loc Location) {
// findRegFor finds a register we can use to make a temp copy of type typ.
func (e *edgeState) findRegFor(typ *types.Type) Location {
// Which registers are possibilities.
- types := &e.s.f.Config.Types
m := e.s.compatRegs(typ)
// Pick a register. In priority order:
@@ -2724,9 +2723,7 @@ func (e *edgeState) findRegFor(typ *types.Type) Location {
if !c.rematerializeable() {
x := e.p.NewValue1(c.Pos, OpStoreReg, c.Type, c)
// Allocate a temp location to spill a register to.
- // The type of the slot is immaterial - it will not be live across
- // any safepoint. Just use a type big enough to hold any register.
- t := LocalSlot{N: e.s.f.NewLocal(c.Pos, types.Int64), Type: types.Int64}
+ t := LocalSlot{N: e.s.f.NewLocal(c.Pos, c.Type), Type: c.Type}
// TODO: reuse these slots. They'll need to be erased first.
e.set(t, vid, x, false, c.Pos)
if e.s.f.pass.debug > regDebug {
diff --git a/src/cmd/compile/internal/ssa/value.go b/src/cmd/compile/internal/ssa/value.go
index 55ab23ce9a0..51a70c7fd4f 100644
--- a/src/cmd/compile/internal/ssa/value.go
+++ b/src/cmd/compile/internal/ssa/value.go
@@ -600,7 +600,7 @@ func (v *Value) removeable() bool {
func AutoVar(v *Value) (*ir.Name, int64) {
if loc, ok := v.Block.Func.RegAlloc[v.ID].(LocalSlot); ok {
if v.Type.Size() > loc.Type.Size() {
- v.Fatalf("spill/restore type %s doesn't fit in slot type %s", v.Type, loc.Type)
+ v.Fatalf("v%d: spill/restore type %v doesn't fit in slot type %v", v.ID, v.Type, loc.Type)
}
return loc.N, loc.Off
}
From ad3db2562edf23cb4fb9a909ea11d57b65e304fb Mon Sep 17 00:00:00 2001
From: Junyang Shao
Date: Tue, 16 Sep 2025 03:27:41 +0000
Subject: [PATCH 010/421] cmd/compile: handle rematerialized op for
incompatible reg constraint
This CL fixes an issue raised by contributor dominikh@.
Cherry-picked from the dev.simd branch. This CL is not
necessarily SIMD specific. Apply early to reduce risk. Test is
SIMD specific so not included for now.
Change-Id: I941b330a6ba6f6c120c69951ddd24933f2f0b3ec
Reviewed-on: https://go-review.googlesource.com/c/go/+/704056
LUCI-TryBot-Result: Go LUCI
Reviewed-by: David Chase
Reviewed-on: https://go-review.googlesource.com/c/go/+/708861
---
src/cmd/compile/internal/ssa/regalloc.go | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/src/cmd/compile/internal/ssa/regalloc.go b/src/cmd/compile/internal/ssa/regalloc.go
index 26c742d7fe7..88861dfa14c 100644
--- a/src/cmd/compile/internal/ssa/regalloc.go
+++ b/src/cmd/compile/internal/ssa/regalloc.go
@@ -2561,7 +2561,26 @@ func (e *edgeState) processDest(loc Location, vid ID, splice **Value, pos src.XP
e.s.f.Fatalf("can't find source for %s->%s: %s\n", e.p, e.b, v.LongString())
}
if dstReg {
- x = v.copyInto(e.p)
+ // Handle incompatible registers.
+ // For #70451.
+ if e.s.regspec(v).outputs[0].regs®Mask(1<
Date: Wed, 17 Sep 2025 14:25:16 -0400
Subject: [PATCH 011/421] cmd/compile: enhance the chunked indexing case to
include reslicing
this helps SIMD, but also helps plain old Go
Cherry-picked from the dev.simd branch. This CL is not
necessarily SIMD specific. Apply early to reduce risk.
Change-Id: Idcdacd54b6776f5c32b497bc94485052611cfa8d
Reviewed-on: https://go-review.googlesource.com/c/go/+/704756
Reviewed-by: Keith Randall
Reviewed-by: Keith Randall
LUCI-TryBot-Result: Go LUCI
Reviewed-on: https://go-review.googlesource.com/c/go/+/708862
Reviewed-by: David Chase
---
src/cmd/compile/internal/ssa/prove.go | 34 ++++++++++++++++++++-------
1 file changed, 26 insertions(+), 8 deletions(-)
diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go
index 7b860a6f9ea..b1d49812c7d 100644
--- a/src/cmd/compile/internal/ssa/prove.go
+++ b/src/cmd/compile/internal/ssa/prove.go
@@ -2177,10 +2177,18 @@ func unsignedSubUnderflows(a, b uint64) bool {
// checkForChunkedIndexBounds looks for index expressions of the form
// A[i+delta] where delta < K and i <= len(A)-K. That is, this is a chunked
// iteration where the index is not directly compared to the length.
-func checkForChunkedIndexBounds(ft *factsTable, b *Block, index, bound *Value) bool {
- if bound.Op != OpSliceLen {
+// if isReslice, then delta can be equal to K.
+func checkForChunkedIndexBounds(ft *factsTable, b *Block, index, bound *Value, isReslice bool) bool {
+ if bound.Op != OpSliceLen && bound.Op != OpSliceCap {
return false
}
+
+ // this is a slice bounds check against len or capacity,
+ // and refers back to a prior check against length, which
+ // will also work for the cap since that is not smaller
+ // than the length.
+
+ slice := bound.Args[0]
lim := ft.limits[index.ID]
if lim.min < 0 {
return false
@@ -2206,9 +2214,9 @@ func checkForChunkedIndexBounds(ft *factsTable, b *Block, index, bound *Value) b
}
if ow := o.w; ow.Op == OpAdd64 {
var lenOffset *Value
- if ow.Args[0] == bound {
+ if bound := ow.Args[0]; bound.Op == OpSliceLen && bound.Args[0] == slice {
lenOffset = ow.Args[1]
- } else if ow.Args[1] == bound {
+ } else if bound := ow.Args[1]; bound.Op == OpSliceLen && bound.Args[0] == slice {
lenOffset = ow.Args[0]
}
if lenOffset == nil || lenOffset.Op != OpConst64 {
@@ -2216,12 +2224,15 @@ func checkForChunkedIndexBounds(ft *factsTable, b *Block, index, bound *Value) b
}
if K := -lenOffset.AuxInt; K >= 0 {
or := o.r
+ if isReslice {
+ K++
+ }
if or == lt {
or = lt | eq
K++
- if K < 0 {
- continue
- }
+ }
+ if K < 0 { // We hate thinking about overflow
+ continue
}
if delta < K && or == lt|eq {
@@ -2345,12 +2356,19 @@ func addLocalFacts(ft *factsTable, b *Block) {
ft.update(b, v, v.Args[0].Args[2], signed, eq)
}
case OpIsInBounds:
- if checkForChunkedIndexBounds(ft, b, v.Args[0], v.Args[1]) {
+ if checkForChunkedIndexBounds(ft, b, v.Args[0], v.Args[1], false) {
if b.Func.pass.debug > 0 {
b.Func.Warnl(v.Pos, "Proved %s for blocked indexing", v.Op)
}
ft.booleanTrue(v)
}
+ case OpIsSliceInBounds:
+ if checkForChunkedIndexBounds(ft, b, v.Args[0], v.Args[1], true) {
+ if b.Func.pass.debug > 0 {
+ b.Func.Warnl(v.Pos, "Proved %s for blocked reslicing", v.Op)
+ }
+ ft.booleanTrue(v)
+ }
case OpPhi:
addLocalFactsPhi(ft, v)
}
From d91148c7a8b2d774ddea5c66c170d24937195df5 Mon Sep 17 00:00:00 2001
From: David Chase
Date: Wed, 17 Sep 2025 17:19:15 -0400
Subject: [PATCH 012/421] cmd/compile: enhance prove to infer bounds in slice
len/cap calculations
the example comes up in chunked reslicing, e.g. A[i:] where i
has a relationship with len(A)-K.
Cherry-picked from the dev.simd branch. This CL is not
necessarily SIMD specific. Apply early to reduce risk.
Change-Id: Ib97dede6cfc7bbbd27b4f384988f741760686604
Reviewed-on: https://go-review.googlesource.com/c/go/+/704875
Reviewed-by: Keith Randall
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Keith Randall
Reviewed-on: https://go-review.googlesource.com/c/go/+/708863
Reviewed-by: David Chase
---
src/cmd/compile/internal/ssa/prove.go | 65 ++++++++++++++++++++++++++-
1 file changed, 64 insertions(+), 1 deletion(-)
diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go
index b1d49812c7d..5ed5be47443 100644
--- a/src/cmd/compile/internal/ssa/prove.go
+++ b/src/cmd/compile/internal/ssa/prove.go
@@ -1766,7 +1766,8 @@ func (ft *factsTable) flowLimit(v *Value) bool {
b := ft.limits[v.Args[1].ID]
sub := ft.newLimit(v, a.sub(b, uint(v.Type.Size())*8))
mod := ft.detectSignedMod(v)
- return sub || mod
+ inferred := ft.detectSliceLenRelation(v)
+ return sub || mod || inferred
case OpNeg64, OpNeg32, OpNeg16, OpNeg8:
a := ft.limits[v.Args[0].ID]
bitsize := uint(v.Type.Size()) * 8
@@ -1947,6 +1948,68 @@ func (ft *factsTable) detectSignedMod(v *Value) bool {
// TODO: non-powers-of-2
return false
}
+
+// detectSliceLenRelation matches the pattern where
+// 1. v := slicelen - index, OR v := slicecap - index
+// AND
+// 2. index <= slicelen - K
+// THEN
+//
+// slicecap - index >= slicelen - index >= K
+//
+// Note that "index" is not useed for indexing in this pattern, but
+// in the motivating example (chunked slice iteration) it is.
+func (ft *factsTable) detectSliceLenRelation(v *Value) (inferred bool) {
+ if v.Op != OpSub64 {
+ return false
+ }
+
+ if !(v.Args[0].Op == OpSliceLen || v.Args[0].Op == OpSliceCap) {
+ return false
+ }
+
+ slice := v.Args[0].Args[0]
+ index := v.Args[1]
+
+ for o := ft.orderings[index.ID]; o != nil; o = o.next {
+ if o.d != signed {
+ continue
+ }
+ or := o.r
+ if or != lt && or != lt|eq {
+ continue
+ }
+ ow := o.w
+ if ow.Op != OpAdd64 && ow.Op != OpSub64 {
+ continue
+ }
+ var lenOffset *Value
+ if bound := ow.Args[0]; bound.Op == OpSliceLen && bound.Args[0] == slice {
+ lenOffset = ow.Args[1]
+ } else if bound := ow.Args[1]; bound.Op == OpSliceLen && bound.Args[0] == slice {
+ lenOffset = ow.Args[0]
+ }
+ if lenOffset == nil || lenOffset.Op != OpConst64 {
+ continue
+ }
+ K := lenOffset.AuxInt
+ if ow.Op == OpAdd64 {
+ K = -K
+ }
+ if K < 0 {
+ continue
+ }
+ if or == lt {
+ K++
+ }
+ if K < 0 { // We hate thinking about overflow
+ continue
+ }
+ inferred = inferred || ft.signedMin(v, K)
+ }
+ return inferred
+}
+
func (ft *factsTable) detectSignedModByPowerOfTwo(v *Value) bool {
// We're looking for:
//
From 003b5ce1bc15cf265e74ba1ec4eb7cf801e49986 Mon Sep 17 00:00:00 2001
From: Junyang Shao
Date: Fri, 19 Sep 2025 18:38:25 +0000
Subject: [PATCH 013/421] cmd/compile: fix SIMD const rematerialization
condition
This CL fixes a condition for the previous fix CL 704056.
Cherry-picked from the dev.simd branch. This CL is not
necessarily SIMD specific. Apply early to reduce risk. Test is
SIMD specific so not included for now.
Change-Id: I1f1f8c6f72870403cb3dff14755c43385dc0c933
Reviewed-on: https://go-review.googlesource.com/c/go/+/705499
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Cherry Mui
Reviewed-on: https://go-review.googlesource.com/c/go/+/708864
Reviewed-by: David Chase
---
src/cmd/compile/internal/ssa/regalloc.go | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/src/cmd/compile/internal/ssa/regalloc.go b/src/cmd/compile/internal/ssa/regalloc.go
index 88861dfa14c..e959b8ed7df 100644
--- a/src/cmd/compile/internal/ssa/regalloc.go
+++ b/src/cmd/compile/internal/ssa/regalloc.go
@@ -2561,22 +2561,25 @@ func (e *edgeState) processDest(loc Location, vid ID, splice **Value, pos src.XP
e.s.f.Fatalf("can't find source for %s->%s: %s\n", e.p, e.b, v.LongString())
}
if dstReg {
- // Handle incompatible registers.
+ // We want to rematerialize v into a register that is incompatible with v's op's register mask.
+ // Instead of setting the wrong register for the rematerialized v, we should find the right register
+ // for it and emit an additional copy to move to the desired register.
// For #70451.
- if e.s.regspec(v).outputs[0].regs®Mask(1<
Date: Mon, 22 Sep 2025 10:57:29 -0400
Subject: [PATCH 014/421] cmd/compile: remove stores to unread parameters
Currently, we remove stores to local variables that are not read.
We don't do that for arguments. But arguments and locals are
essentially the same. Arguments are passed by value, and are not
expected to be read in the caller's frame. So we can remove the
writes to them as well. One exception is the cgo_unsafe_arg
directive, which makes all the arguments effectively address-taken.
cgo_unsafe_arg implies ABI0, so we just skip ABI0 functions'
arguments.
Cherry-picked from the dev.simd branch. This CL is not
necessarily SIMD specific. Apply early to reduce risk.
Change-Id: I8999fc50da6a87f22c1ec23e9a0c15483b6f7df8
Reviewed-on: https://go-review.googlesource.com/c/go/+/705815
LUCI-TryBot-Result: Go LUCI
Reviewed-by: David Chase
Reviewed-by: Junyang Shao
Reviewed-on: https://go-review.googlesource.com/c/go/+/708865
---
src/cmd/compile/internal/ssa/deadstore.go | 22 +++++++++++++++----
src/runtime/testdata/testprog/badtraceback.go | 2 ++
test/codegen/stack.go | 6 +++++
3 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/src/cmd/compile/internal/ssa/deadstore.go b/src/cmd/compile/internal/ssa/deadstore.go
index 9e67e833999..d0adff788c0 100644
--- a/src/cmd/compile/internal/ssa/deadstore.go
+++ b/src/cmd/compile/internal/ssa/deadstore.go
@@ -7,6 +7,7 @@ package ssa
import (
"cmd/compile/internal/ir"
"cmd/compile/internal/types"
+ "cmd/internal/obj"
)
// dse does dead-store elimination on the Function.
@@ -213,7 +214,7 @@ func elimDeadAutosGeneric(f *Func) {
case OpAddr, OpLocalAddr:
// Propagate the address if it points to an auto.
n, ok := v.Aux.(*ir.Name)
- if !ok || n.Class != ir.PAUTO {
+ if !ok || (n.Class != ir.PAUTO && !isABIInternalParam(f, n)) {
return
}
if addr[v] == nil {
@@ -224,7 +225,7 @@ func elimDeadAutosGeneric(f *Func) {
case OpVarDef:
// v should be eliminated if we eliminate the auto.
n, ok := v.Aux.(*ir.Name)
- if !ok || n.Class != ir.PAUTO {
+ if !ok || (n.Class != ir.PAUTO && !isABIInternalParam(f, n)) {
return
}
if elim[v] == nil {
@@ -240,7 +241,7 @@ func elimDeadAutosGeneric(f *Func) {
// may not be used by the inline code, but will be used by
// panic processing).
n, ok := v.Aux.(*ir.Name)
- if !ok || n.Class != ir.PAUTO {
+ if !ok || (n.Class != ir.PAUTO && !isABIInternalParam(f, n)) {
return
}
if !used.Has(n) {
@@ -373,7 +374,7 @@ func elimUnreadAutos(f *Func) {
if !ok {
continue
}
- if n.Class != ir.PAUTO {
+ if n.Class != ir.PAUTO && !isABIInternalParam(f, n) {
continue
}
@@ -413,3 +414,16 @@ func elimUnreadAutos(f *Func) {
store.Op = OpCopy
}
}
+
+// isABIInternalParam returns whether n is a parameter of an ABIInternal
+// function. For dead store elimination, we can treat parameters the same
+// way as autos. Storing to a parameter can be removed if it is not read
+// or address-taken.
+//
+// We check ABI here because for a cgo_unsafe_arg function (which is ABI0),
+// all the args are effectively address-taken, but not necessarily have
+// an Addr or LocalAddr op. We could probably just check for cgo_unsafe_arg,
+// but ABIInternal is mostly what matters.
+func isABIInternalParam(f *Func, n *ir.Name) bool {
+ return n.Class == ir.PPARAM && f.ABISelf.Which() == obj.ABIInternal
+}
diff --git a/src/runtime/testdata/testprog/badtraceback.go b/src/runtime/testdata/testprog/badtraceback.go
index 09aa2b877ec..455118a5437 100644
--- a/src/runtime/testdata/testprog/badtraceback.go
+++ b/src/runtime/testdata/testprog/badtraceback.go
@@ -44,6 +44,8 @@ func badLR2(arg int) {
lrPtr := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&arg)) - lrOff))
*lrPtr = 0xbad
+ runtime.KeepAlive(lrPtr) // prevent dead store elimination
+
// Print a backtrace. This should include diagnostics for the
// bad return PC and a hex dump.
panic("backtrace")
diff --git a/test/codegen/stack.go b/test/codegen/stack.go
index 4e45d68f381..59284ae8886 100644
--- a/test/codegen/stack.go
+++ b/test/codegen/stack.go
@@ -168,3 +168,9 @@ func getp1() *[4]int {
func getp2() *[4]int {
return nil
}
+
+// Store to an argument without read can be removed.
+func storeArg(a [2]int) {
+ // amd64:-`MOVQ\t\$123,.*\.a\+\d+\(SP\)`
+ a[1] = 123
+}
From 1bca4c1673f5d90822086f34aed6de4a9bea2d93 Mon Sep 17 00:00:00 2001
From: David Chase
Date: Wed, 17 Sep 2025 17:21:37 -0400
Subject: [PATCH 015/421] cmd/compile: improve slicemask removal
this will be subsumed by pending changes in local slice
representation, however this was easy and works well.
Cherry-picked from the dev.simd branch. This CL is not
necessarily SIMD specific. Apply early to reduce risk.
Change-Id: I5b6eb10d257f04f906be7a8a6f2b6833992a39e8
Reviewed-on: https://go-review.googlesource.com/c/go/+/704876
Reviewed-by: Keith Randall
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Keith Randall
Reviewed-on: https://go-review.googlesource.com/c/go/+/708866
Reviewed-by: David Chase
---
src/cmd/compile/internal/ssa/prove.go | 30 +++++--
test/loopbce.go | 118 +++++++++++++-------------
2 files changed, 81 insertions(+), 67 deletions(-)
diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go
index 5ed5be47443..b4f91fd4fd1 100644
--- a/src/cmd/compile/internal/ssa/prove.go
+++ b/src/cmd/compile/internal/ssa/prove.go
@@ -2529,24 +2529,38 @@ func simplifyBlock(sdom SparseTree, ft *factsTable, b *Block) {
switch v.Op {
case OpSlicemask:
// Replace OpSlicemask operations in b with constants where possible.
- x, delta := isConstDelta(v.Args[0])
- if x == nil {
+ cap := v.Args[0]
+ x, delta := isConstDelta(cap)
+ if x != nil {
+ // slicemask(x + y)
+ // if x is larger than -y (y is negative), then slicemask is -1.
+ lim := ft.limits[x.ID]
+ if lim.umin > uint64(-delta) {
+ if cap.Op == OpAdd64 {
+ v.reset(OpConst64)
+ } else {
+ v.reset(OpConst32)
+ }
+ if b.Func.pass.debug > 0 {
+ b.Func.Warnl(v.Pos, "Proved slicemask not needed")
+ }
+ v.AuxInt = -1
+ }
break
}
- // slicemask(x + y)
- // if x is larger than -y (y is negative), then slicemask is -1.
- lim := ft.limits[x.ID]
- if lim.umin > uint64(-delta) {
- if v.Args[0].Op == OpAdd64 {
+ lim := ft.limits[cap.ID]
+ if lim.umin > 0 {
+ if cap.Type.Size() == 8 {
v.reset(OpConst64)
} else {
v.reset(OpConst32)
}
if b.Func.pass.debug > 0 {
- b.Func.Warnl(v.Pos, "Proved slicemask not needed")
+ b.Func.Warnl(v.Pos, "Proved slicemask not needed (by limit)")
}
v.AuxInt = -1
}
+
case OpCtz8, OpCtz16, OpCtz32, OpCtz64:
// On some architectures, notably amd64, we can generate much better
// code for CtzNN if we know that the argument is non-zero.
diff --git a/test/loopbce.go b/test/loopbce.go
index 8bc44ece945..8a58d942361 100644
--- a/test/loopbce.go
+++ b/test/loopbce.go
@@ -9,7 +9,7 @@ import "math"
func f0a(a []int) int {
x := 0
for i := range a { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
- x += a[i] // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ x += a[i] // ERROR "Proved IsInBounds$"
}
return x
}
@@ -17,7 +17,7 @@ func f0a(a []int) int {
func f0b(a []int) int {
x := 0
for i := range a { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
- b := a[i:] // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
+ b := a[i:] // ERROR "Proved IsSliceInBounds$"
x += b[0]
}
return x
@@ -26,8 +26,8 @@ func f0b(a []int) int {
func f0c(a []int) int {
x := 0
for i := range a { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
- b := a[:i+1] // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
- x += b[0] // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ b := a[:i+1] // ERROR "Proved IsSliceInBounds$"
+ x += b[0] // ERROR "Proved IsInBounds$"
}
return x
}
@@ -43,7 +43,7 @@ func f1(a []int) int {
func f2(a []int) int {
x := 0
for i := 1; i < len(a); i++ { // ERROR "Induction variable: limits \[1,\?\), increment 1$"
- x += a[i] // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ x += a[i] // ERROR "Proved IsInBounds$"
}
return x
}
@@ -51,7 +51,7 @@ func f2(a []int) int {
func f4(a [10]int) int {
x := 0
for i := 0; i < len(a); i += 2 { // ERROR "Induction variable: limits \[0,8\], increment 2$"
- x += a[i] // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ x += a[i] // ERROR "Proved IsInBounds$"
}
return x
}
@@ -91,7 +91,7 @@ func f5_int8(a [10]int) int {
//go:noinline
func f6(a []int) {
for i := range a { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
- b := a[0:i] // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
+ b := a[0:i] // ERROR "Proved IsSliceInBounds$"
f6(b)
}
}
@@ -99,7 +99,7 @@ func f6(a []int) {
func g0a(a string) int {
x := 0
for i := 0; i < len(a); i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
- x += int(a[i]) // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ x += int(a[i]) // ERROR "Proved IsInBounds$"
}
return x
}
@@ -107,7 +107,7 @@ func g0a(a string) int {
func g0b(a string) int {
x := 0
for i := 0; len(a) > i; i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
- x += int(a[i]) // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ x += int(a[i]) // ERROR "Proved IsInBounds$"
}
return x
}
@@ -115,7 +115,7 @@ func g0b(a string) int {
func g0c(a string) int {
x := 0
for i := len(a); i > 0; i-- { // ERROR "Induction variable: limits \(0,\?\], increment 1$"
- x += int(a[i-1]) // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ x += int(a[i-1]) // ERROR "Proved IsInBounds$"
}
return x
}
@@ -123,7 +123,7 @@ func g0c(a string) int {
func g0d(a string) int {
x := 0
for i := len(a); 0 < i; i-- { // ERROR "Induction variable: limits \(0,\?\], increment 1$"
- x += int(a[i-1]) // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ x += int(a[i-1]) // ERROR "Proved IsInBounds$"
}
return x
}
@@ -131,7 +131,7 @@ func g0d(a string) int {
func g0e(a string) int {
x := 0
for i := len(a) - 1; i >= 0; i-- { // ERROR "Induction variable: limits \[0,\?\], increment 1$"
- x += int(a[i]) // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ x += int(a[i]) // ERROR "Proved IsInBounds$"
}
return x
}
@@ -139,7 +139,7 @@ func g0e(a string) int {
func g0f(a string) int {
x := 0
for i := len(a) - 1; 0 <= i; i-- { // ERROR "Induction variable: limits \[0,\?\], increment 1$"
- x += int(a[i]) // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ x += int(a[i]) // ERROR "Proved IsInBounds$"
}
return x
}
@@ -148,7 +148,7 @@ func g1() int {
a := "evenlength"
x := 0
for i := 0; i < len(a); i += 2 { // ERROR "Induction variable: limits \[0,8\], increment 2$"
- x += int(a[i]) // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ x += int(a[i]) // ERROR "Proved IsInBounds$"
}
return x
}
@@ -158,7 +158,7 @@ func g2() int {
x := 0
for i := 0; i < len(a); i += 2 { // ERROR "Induction variable: limits \[0,8\], increment 2$"
j := i
- if a[i] == 'e' { // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ if a[i] == 'e' { // ERROR "Proved IsInBounds$"
j = j + 1
}
x += int(a[j])
@@ -169,29 +169,29 @@ func g2() int {
func g3a() {
a := "this string has length 25"
for i := 0; i < len(a); i += 5 { // ERROR "Induction variable: limits \[0,20\], increment 5$"
- useString(a[i:]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
- useString(a[:i+3]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
- useString(a[:i+5]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
+ useString(a[i:]) // ERROR "Proved IsSliceInBounds$" "Proved slicemask not needed \(by limit\)$"
+ useString(a[:i+3]) // ERROR "Proved IsSliceInBounds$"
+ useString(a[:i+5]) // ERROR "Proved IsSliceInBounds$"
useString(a[:i+6])
}
}
func g3b(a string) {
for i := 0; i < len(a); i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
- useString(a[i+1:]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
+ useString(a[i+1:]) // ERROR "Proved IsSliceInBounds$"
}
}
func g3c(a string) {
for i := 0; i < len(a); i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
- useString(a[:i+1]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
+ useString(a[:i+1]) // ERROR "Proved IsSliceInBounds$"
}
}
func h1(a []byte) {
c := a[:128]
for i := range c { // ERROR "Induction variable: limits \[0,128\), increment 1$"
- c[i] = byte(i) // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ c[i] = byte(i) // ERROR "Proved IsInBounds$"
}
}
@@ -208,11 +208,11 @@ func k0(a [100]int) [100]int {
continue
}
a[i-11] = i
- a[i-10] = i // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
- a[i-5] = i // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
- a[i] = i // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
- a[i+5] = i // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
- a[i+10] = i // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ a[i-10] = i // ERROR "Proved IsInBounds$"
+ a[i-5] = i // ERROR "Proved IsInBounds$"
+ a[i] = i // ERROR "Proved IsInBounds$"
+ a[i+5] = i // ERROR "Proved IsInBounds$"
+ a[i+10] = i // ERROR "Proved IsInBounds$"
a[i+11] = i
}
return a
@@ -225,12 +225,12 @@ func k1(a [100]int) [100]int {
continue
}
useSlice(a[:i-11])
- useSlice(a[:i-10]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
- useSlice(a[:i-5]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
- useSlice(a[:i]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
- useSlice(a[:i+5]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
- useSlice(a[:i+10]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
- useSlice(a[:i+11]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
+ useSlice(a[:i-10]) // ERROR "Proved IsSliceInBounds$"
+ useSlice(a[:i-5]) // ERROR "Proved IsSliceInBounds$"
+ useSlice(a[:i]) // ERROR "Proved IsSliceInBounds$"
+ useSlice(a[:i+5]) // ERROR "Proved IsSliceInBounds$"
+ useSlice(a[:i+10]) // ERROR "Proved IsSliceInBounds$"
+ useSlice(a[:i+11]) // ERROR "Proved IsSliceInBounds$"
useSlice(a[:i+12])
}
@@ -243,13 +243,13 @@ func k2(a [100]int) [100]int {
// This is a trick to prohibit sccp to optimize out the following out of bound check
continue
}
- useSlice(a[i-11:])
- useSlice(a[i-10:]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
- useSlice(a[i-5:]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
- useSlice(a[i:]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
- useSlice(a[i+5:]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
- useSlice(a[i+10:]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
- useSlice(a[i+11:]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
+ useSlice(a[i-11:]) // ERROR "Proved slicemask not needed \(by limit\)$"
+ useSlice(a[i-10:]) // ERROR "Proved IsSliceInBounds$" "Proved slicemask not needed \(by limit\)$"
+ useSlice(a[i-5:]) // ERROR "Proved IsSliceInBounds$" "Proved slicemask not needed \(by limit\)$"
+ useSlice(a[i:]) // ERROR "Proved IsSliceInBounds$" "Proved slicemask not needed \(by limit\)$"
+ useSlice(a[i+5:]) // ERROR "Proved IsSliceInBounds$" "Proved slicemask not needed \(by limit\)$"
+ useSlice(a[i+10:]) // ERROR "Proved IsSliceInBounds$" "Proved slicemask not needed \(by limit\)$"
+ useSlice(a[i+11:]) // ERROR "Proved IsSliceInBounds$"
useSlice(a[i+12:])
}
return a
@@ -262,7 +262,7 @@ func k3(a [100]int) [100]int {
continue
}
a[i+9] = i
- a[i+10] = i // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ a[i+10] = i // ERROR "Proved IsInBounds$"
a[i+11] = i
}
return a
@@ -275,7 +275,7 @@ func k3neg(a [100]int) [100]int {
continue
}
a[i+9] = i
- a[i+10] = i // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ a[i+10] = i // ERROR "Proved IsInBounds$"
a[i+11] = i
}
return a
@@ -288,7 +288,7 @@ func k3neg2(a [100]int) [100]int {
continue
}
a[i+9] = i
- a[i+10] = i // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ a[i+10] = i // ERROR "Proved IsInBounds$"
a[i+11] = i
}
return a
@@ -299,7 +299,7 @@ func k4(a [100]int) [100]int {
// and it isn't worth adding that special case to prove.
min := (-1)<<63 + 1
for i := min; i < min+50; i++ { // ERROR "Induction variable: limits \[-9223372036854775807,-9223372036854775757\), increment 1$"
- a[i-min] = i // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ a[i-min] = i // ERROR "Proved IsInBounds$"
}
return a
}
@@ -307,8 +307,8 @@ func k4(a [100]int) [100]int {
func k5(a [100]int) [100]int {
max := (1 << 63) - 1
for i := max - 50; i < max; i++ { // ERROR "Induction variable: limits \[9223372036854775757,9223372036854775807\), increment 1$"
- a[i-max+50] = i // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
- a[i-(max-70)] = i // ERROR "(\([0-9]+\) )?Proved IsInBounds$"
+ a[i-max+50] = i // ERROR "Proved IsInBounds$"
+ a[i-(max-70)] = i // ERROR "Proved IsInBounds$"
}
return a
}
@@ -374,22 +374,22 @@ func d4() {
}
func d5() {
- for i := int64(math.MinInt64 + 9); i > math.MinInt64+2; i -= 4 { // ERROR "Induction variable: limits \[-9223372036854775803,-9223372036854775799\], increment 4"
+ for i := int64(math.MinInt64 + 9); i > math.MinInt64+2; i -= 4 { // ERROR "Induction variable: limits \[-9223372036854775803,-9223372036854775799\], increment 4$"
useString("foo")
}
- for i := int64(math.MinInt64 + 8); i > math.MinInt64+2; i -= 4 { // ERROR "Induction variable: limits \[-9223372036854775804,-9223372036854775800\], increment 4"
+ for i := int64(math.MinInt64 + 8); i > math.MinInt64+2; i -= 4 { // ERROR "Induction variable: limits \[-9223372036854775804,-9223372036854775800\], increment 4$"
useString("foo")
}
for i := int64(math.MinInt64 + 7); i > math.MinInt64+2; i -= 4 {
useString("foo")
}
- for i := int64(math.MinInt64 + 6); i > math.MinInt64+2; i -= 4 { // ERROR "Induction variable: limits \[-9223372036854775802,-9223372036854775802\], increment 4"
+ for i := int64(math.MinInt64 + 6); i > math.MinInt64+2; i -= 4 { // ERROR "Induction variable: limits \[-9223372036854775802,-9223372036854775802\], increment 4$"
useString("foo")
}
- for i := int64(math.MinInt64 + 9); i >= math.MinInt64+2; i -= 4 { // ERROR "Induction variable: limits \[-9223372036854775803,-9223372036854775799\], increment 4"
+ for i := int64(math.MinInt64 + 9); i >= math.MinInt64+2; i -= 4 { // ERROR "Induction variable: limits \[-9223372036854775803,-9223372036854775799\], increment 4$"
useString("foo")
}
- for i := int64(math.MinInt64 + 8); i >= math.MinInt64+2; i -= 4 { // ERROR "Induction variable: limits \[-9223372036854775804,-9223372036854775800\], increment 4"
+ for i := int64(math.MinInt64 + 8); i >= math.MinInt64+2; i -= 4 { // ERROR "Induction variable: limits \[-9223372036854775804,-9223372036854775800\], increment 4$"
useString("foo")
}
for i := int64(math.MinInt64 + 7); i >= math.MinInt64+2; i -= 4 {
@@ -410,23 +410,23 @@ func bce1() {
panic("invalid test: modulos should differ")
}
- for i := b; i < a; i += z { // ERROR "Induction variable: limits \[-1547,9223372036854772720\], increment 1337"
+ for i := b; i < a; i += z { // ERROR "Induction variable: limits \[-1547,9223372036854772720\], increment 1337$"
useString("foobar")
}
}
func nobce2(a string) {
for i := int64(0); i < int64(len(a)); i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
- useString(a[i:]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
+ useString(a[i:]) // ERROR "Proved IsSliceInBounds$"
}
for i := int64(0); i < int64(len(a))-31337; i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
- useString(a[i:]) // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
+ useString(a[i:]) // ERROR "Proved IsSliceInBounds$"
}
- for i := int64(0); i < int64(len(a))+int64(-1<<63); i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$" "Disproved Less64"
+ for i := int64(0); i < int64(len(a))+int64(-1<<63); i++ { // ERROR "Disproved Less64$" "Induction variable: limits \[0,\?\), increment 1$"
useString(a[i:])
}
j := int64(len(a)) - 123
- for i := int64(0); i < j+123+int64(-1<<63); i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$" "Disproved Less64"
+ for i := int64(0); i < j+123+int64(-1<<63); i++ { // ERROR "Disproved Less64$" "Induction variable: limits \[0,\?\), increment 1$"
useString(a[i:])
}
for i := int64(0); i < j+122+int64(-1<<63); i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
@@ -455,16 +455,16 @@ func issue26116a(a []int) {
func stride1(x *[7]int) int {
s := 0
- for i := 0; i <= 8; i += 3 { // ERROR "Induction variable: limits \[0,6\], increment 3"
- s += x[i] // ERROR "Proved IsInBounds"
+ for i := 0; i <= 8; i += 3 { // ERROR "Induction variable: limits \[0,6\], increment 3$"
+ s += x[i] // ERROR "Proved IsInBounds$"
}
return s
}
func stride2(x *[7]int) int {
s := 0
- for i := 0; i < 9; i += 3 { // ERROR "Induction variable: limits \[0,6\], increment 3"
- s += x[i] // ERROR "Proved IsInBounds"
+ for i := 0; i < 9; i += 3 { // ERROR "Induction variable: limits \[0,6\], increment 3$"
+ s += x[i] // ERROR "Proved IsInBounds$"
}
return s
}
From ee5369b003b17b34aa6417cf8c9b702f1cd76da1 Mon Sep 17 00:00:00 2001
From: qmuntal
Date: Fri, 3 Oct 2025 11:18:47 +0200
Subject: [PATCH 016/421] cmd/link: add LIBRARY statement only with
-buildmode=cshared
When creating a .def file for Windows linking, add a LIBRARY statement
only when building a DLL with -buildmode=cshared. That statement is
documented to instruct the linker to create a DLL, overriding any
other flag that might indicate building an executable.
Fixes #75734
Change-Id: I0231435df70b71a493a39deb639f6328a8e354f6
Reviewed-on: https://go-review.googlesource.com/c/go/+/708815
Reviewed-by: Carlos Amedee
Reviewed-by: Dominic Della Valle
Reviewed-by: Cherry Mui
LUCI-TryBot-Result: Go LUCI
---
src/cmd/link/internal/ld/pe.go | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/cmd/link/internal/ld/pe.go b/src/cmd/link/internal/ld/pe.go
index 0f0650e5e14..e0186f46b03 100644
--- a/src/cmd/link/internal/ld/pe.go
+++ b/src/cmd/link/internal/ld/pe.go
@@ -1758,7 +1758,9 @@ func peCreateExportFile(ctxt *Link, libName string) (fname string) {
fname = filepath.Join(*flagTmpdir, "export_file.def")
var buf bytes.Buffer
- fmt.Fprintf(&buf, "LIBRARY %s\n", libName)
+ if ctxt.BuildMode == BuildModeCShared {
+ fmt.Fprintf(&buf, "LIBRARY %s\n", libName)
+ }
buf.WriteString("EXPORTS\n")
ldr := ctxt.loader
From 2a71af11fc7e45903be9ab84e59c668bb051f528 Mon Sep 17 00:00:00 2001
From: Mateusz Poliwczak
Date: Wed, 24 Sep 2025 16:40:55 +0200
Subject: [PATCH 017/421] net/url: improve URL docs
The Raw fields are confusing and easy to use by mistake. Adds more
context in comments to these fields.
Also the current docs (and the names of these fields) of these
boolean fields are not obvious that parser might produce them,
so clarify that
Change-Id: I6a6a69644834c3ccbf657147f771930b6875f721
Reviewed-on: https://go-review.googlesource.com/c/go/+/706515
Reviewed-by: Florian Lehner
Reviewed-by: Sean Liao
Reviewed-by: Damien Neil
Reviewed-by: Carlos Amedee
Auto-Submit: Damien Neil
LUCI-TryBot-Result: Go LUCI
---
src/net/url/url.go | 50 ++++++++++++++++++++++++++++++----------------
1 file changed, 33 insertions(+), 17 deletions(-)
diff --git a/src/net/url/url.go b/src/net/url/url.go
index 2a576594603..015c5b27519 100644
--- a/src/net/url/url.go
+++ b/src/net/url/url.go
@@ -364,25 +364,41 @@ func escape(s string, mode encoding) string {
// A consequence is that it is impossible to tell which slashes in the Path were
// slashes in the raw URL and which were %2f. This distinction is rarely important,
// but when it is, the code should use the [URL.EscapedPath] method, which preserves
-// the original encoding of Path.
+// the original encoding of Path. The Fragment field is also stored in decoded form,
+// use [URL.EscapedFragment] to retrieve the original encoding.
//
-// The RawPath field is an optional field which is only set when the default
-// encoding of Path is different from the escaped path. See the EscapedPath method
-// for more details.
-//
-// URL's String method uses the EscapedPath method to obtain the path.
+// The [URL.String] method uses the [URL.EscapedPath] method to obtain the path.
type URL struct {
- Scheme string
- Opaque string // encoded opaque data
- User *Userinfo // username and password information
- Host string // host or host:port (see Hostname and Port methods)
- Path string // path (relative paths may omit leading slash)
- RawPath string // encoded path hint (see EscapedPath method)
- OmitHost bool // do not emit empty host (authority)
- ForceQuery bool // append a query ('?') even if RawQuery is empty
- RawQuery string // encoded query values, without '?'
- Fragment string // fragment for references, without '#'
- RawFragment string // encoded fragment hint (see EscapedFragment method)
+ Scheme string
+ Opaque string // encoded opaque data
+ User *Userinfo // username and password information
+ Host string // "host" or "host:port" (see Hostname and Port methods)
+ Path string // path (relative paths may omit leading slash)
+ Fragment string // fragment for references (without '#')
+
+ // RawQuery contains the encoded query values, without the initial '?'.
+ // Use URL.Query to decode the query.
+ RawQuery string
+
+ // RawPath is an optional field containing an encoded path hint.
+ // See the EscapedPath method for more details.
+ //
+ // In general, code should call EscapedPath instead of reading RawPath.
+ RawPath string
+
+ // RawFragment is an optional field containing an encoded fragment hint.
+ // See the EscapedFragment method for more details.
+ //
+ // In general, code should call EscapedFragment instead of reading RawFragment.
+ RawFragment string
+
+ // ForceQuery indicates whether the original URL contained a query ('?') character.
+ // When set, the String method will include a trailing '?', even when RawQuery is empty.
+ ForceQuery bool
+
+ // OmitHost indicates the URL has an empty host (authority).
+ // When set, the String method will not include the host when it is empty.
+ OmitHost bool
}
// User returns a [Userinfo] containing the provided username
From 3a05e7b0325eb71fede880f67db63d192f2fa0e1 Mon Sep 17 00:00:00 2001
From: Oliver Eikemeier
Date: Sun, 5 Oct 2025 11:57:40 +0000
Subject: [PATCH 018/421] spec: close tag
Close an "a" tag. While we are here, fix some escapes.
Change-Id: I16040eff0d4beeef6230aec8fcf4315f0efd13a4
GitHub-Last-Rev: 3ba7b9f7478f54338bd3ca7ac55cc2ad1ffcb3a4
GitHub-Pull-Request: golang/go#75760
Reviewed-on: https://go-review.googlesource.com/c/go/+/708517
Reviewed-by: Sean Liao
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Cherry Mui
Reviewed-by: Emmanuel Odeke
Reviewed-by: Robert Findley
Auto-Submit: Sean Liao
---
doc/go_spec.html | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/doc/go_spec.html b/doc/go_spec.html
index 92afe1cee0b..d6c7962a961 100644
--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -3173,7 +3173,7 @@ Element = Expression | LiteralValue .
Unless the LiteralType is a type parameter,
-its underlying type
+its underlying type
must be a struct, array, slice, or map type
(the syntax enforces this constraint except when the type is given
as a TypeName).
@@ -4873,7 +4873,7 @@ For instance, x / y * z is the same as (x / y) * z.
x <= f() // x <= f()
^a >> b // (^a) >> b
f() || g() // f() || g()
-x == y+1 && <-chanInt > 0 // (x == (y+1)) && ((<-chanInt) > 0)
+x == y+1 && <-chanInt > 0 // (x == (y+1)) && ((<-chanInt) > 0)
@@ -6635,7 +6635,7 @@ iteration's variable at that moment.
var prints []func()
-for i := 0; i < 5; i++ {
+for i := 0; i < 5; i++ {
prints = append(prints, func() { println(i) })
i++
}
@@ -6772,7 +6772,7 @@ if the iteration variable is preexisting, the type of the iteration values is th
variable, which must be of integer type.
Otherwise, if the iteration variable is declared by the "range" clause or is absent,
the type of the iteration values is the default type for n.
-If n <= 0, the loop does not run any iterations.
+If n <= 0, the loop does not run any iterations.
-min(x, y) == if x <= y then x else y
+min(x, y) == if x <= y then x else y
min(x, y, z) == min(min(x, y), z)
From e74b224b7c7b7511fe37686d81a6e33d40fdeb17 Mon Sep 17 00:00:00 2001
From: Daniel McCarney
Date: Fri, 11 Jul 2025 10:42:22 -0400
Subject: [PATCH 019/421] crypto/tls: streamline BoGo testing w/
-bogo-local-dir
If -bogo-local-dir is provided but doesn't exist, populate it with a git
checkout of the BoringSSL repo at the correct SHA.
Without any -bogo-local-dir argument the BoGo TLS handshake test will
fetch the BoringSSL source at a specific SHA as a Go module in a r/o
module directory. When debugging, or extending BoGo coverage, it's
preferable to have a mutable local copy of BoGo that the test will
use.
The pre-existing -bogo-local-dir flag offered a way to use a checkout of
BoGo but it relied on the user fetching the correct repo & revision
manually ahead of time. This commit extends the test to automatically
invoke `git` to clone the repo into the provided local dir at the
correct SHA based on the boringsslModVer const if the local dir doesn't
exist.
This leaves the user ready to make changes in local BoGo dir to aid
debugging, or to upstream as CRs to BoringSSL, and prevents using an
incorrect SHA by mistake.
Updates #72006
Change-Id: I0451a3d35203878cdf02a7587e138c3cd60d15a9
Reviewed-on: https://go-review.googlesource.com/c/go/+/687475
Reviewed-by: Roland Shoemaker
Reviewed-by: Carlos Amedee
TryBot-Bypass: Daniel McCarney
---
src/crypto/tls/bogo_shim_test.go | 46 ++++++++++++++++++++++++++++++++
src/crypto/tls/handshake_test.go | 5 ++--
2 files changed, 49 insertions(+), 2 deletions(-)
diff --git a/src/crypto/tls/bogo_shim_test.go b/src/crypto/tls/bogo_shim_test.go
index f3f19b34e9e..8f171d92595 100644
--- a/src/crypto/tls/bogo_shim_test.go
+++ b/src/crypto/tls/bogo_shim_test.go
@@ -11,6 +11,7 @@ import (
"encoding/base64"
"encoding/json"
"encoding/pem"
+ "errors"
"flag"
"fmt"
"html/template"
@@ -541,6 +542,7 @@ func orderlyShutdown(tlsConn *Conn) {
}
func TestBogoSuite(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
if testing.Short() {
t.Skip("skipping in short mode")
}
@@ -559,6 +561,7 @@ func TestBogoSuite(t *testing.T) {
var bogoDir string
if *bogoLocalDir != "" {
+ ensureLocalBogo(t, *bogoLocalDir)
bogoDir = *bogoLocalDir
} else {
bogoDir = cryptotest.FetchModule(t, "boringssl.googlesource.com/boringssl.git", boringsslModVer)
@@ -664,6 +667,49 @@ func TestBogoSuite(t *testing.T) {
}
}
+// ensureLocalBogo fetches BoringSSL to localBogoDir at the correct revision
+// (from boringsslModVer) if localBogoDir doesn't already exist.
+//
+// If localBogoDir does exist, ensureLocalBogo fails the test if it isn't
+// a directory.
+func ensureLocalBogo(t *testing.T, localBogoDir string) {
+ t.Helper()
+
+ if stat, err := os.Stat(localBogoDir); err == nil {
+ if !stat.IsDir() {
+ t.Fatalf("local bogo dir (%q) exists but is not a directory", localBogoDir)
+ }
+
+ t.Logf("using local bogo checkout from %q", localBogoDir)
+ return
+ } else if !errors.Is(err, os.ErrNotExist) {
+ t.Fatalf("failed to stat local bogo dir (%q): %v", localBogoDir, err)
+ }
+
+ testenv.MustHaveExecPath(t, "git")
+
+ idx := strings.LastIndex(boringsslModVer, "-")
+ if idx == -1 || idx == len(boringsslModVer)-1 {
+ t.Fatalf("invalid boringsslModVer format: %q", boringsslModVer)
+ }
+ commitSHA := boringsslModVer[idx+1:]
+
+ t.Logf("cloning boringssl@%s to %q", commitSHA, localBogoDir)
+ cloneCmd := testenv.Command(t, "git", "clone", "--no-checkout", "https://boringssl.googlesource.com/boringssl", localBogoDir)
+ if err := cloneCmd.Run(); err != nil {
+ t.Fatalf("git clone failed: %v", err)
+ }
+
+ checkoutCmd := testenv.Command(t, "git", "checkout", commitSHA)
+ checkoutCmd.Dir = localBogoDir
+ if err := checkoutCmd.Run(); err != nil {
+ t.Fatalf("git checkout failed: %v", err)
+ }
+
+ t.Logf("using fresh local bogo checkout from %q", localBogoDir)
+ return
+}
+
func generateReport(results bogoResults, outPath string) error {
data := reportData{
Results: results,
diff --git a/src/crypto/tls/handshake_test.go b/src/crypto/tls/handshake_test.go
index 13f1bb1dd50..3e2c5663087 100644
--- a/src/crypto/tls/handshake_test.go
+++ b/src/crypto/tls/handshake_test.go
@@ -46,8 +46,9 @@ var (
keyFile = flag.String("keylog", "", "destination file for KeyLogWriter")
bogoMode = flag.Bool("bogo-mode", false, "Enabled bogo shim mode, ignore everything else")
bogoFilter = flag.String("bogo-filter", "", "BoGo test filter")
- bogoLocalDir = flag.String("bogo-local-dir", "", "Local BoGo to use, instead of fetching from source")
- bogoReport = flag.String("bogo-html-report", "", "File path to render an HTML report with BoGo results")
+ bogoLocalDir = flag.String("bogo-local-dir", "",
+ "If not-present, checkout BoGo into this dir, or otherwise use it as a pre-existing checkout")
+ bogoReport = flag.String("bogo-html-report", "", "File path to render an HTML report with BoGo results")
)
func runTestAndUpdateIfNeeded(t *testing.T, name string, run func(t *testing.T, update bool), wait bool) {
From ac2ec82172799b88c057bb9ded6fe24e7909e860 Mon Sep 17 00:00:00 2001
From: Michael Anthony Knyszek
Date: Fri, 3 Oct 2025 16:23:10 +0000
Subject: [PATCH 020/421] runtime: bump thread count slack for
TestReadMetricsSched
This test is *still* flaky, but it appears to be just
mayMoreStackPreempt and the thread count *occasionally* exceeds the
original (and arbitrary) thread count slack by exactly 1.
Bump the thread count slack by one. We can investigate further and bump
it again if it continues to be a problem.
Fixes #75664.
Change-Id: I29c922bba6d2cc99a8c3bf5e04cc512d0694f7fa
Reviewed-on: https://go-review.googlesource.com/c/go/+/708868
Reviewed-by: Michael Pratt
Auto-Submit: Michael Knyszek
LUCI-TryBot-Result: Go LUCI
---
src/runtime/testdata/testprog/schedmetrics.go | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/runtime/testdata/testprog/schedmetrics.go b/src/runtime/testdata/testprog/schedmetrics.go
index 6d3f68a848e..bc0906330f1 100644
--- a/src/runtime/testdata/testprog/schedmetrics.go
+++ b/src/runtime/testdata/testprog/schedmetrics.go
@@ -84,7 +84,12 @@ func SchedMetrics() {
// threadsSlack is the maximum number of threads left over
// from the runtime (sysmon, the template thread, etc.)
- const threadsSlack = 4
+ // Certain build modes may also cause the creation of additional
+ // threads through frequent scheduling, like mayMoreStackPreempt.
+ // A slack of 5 is arbitrary but appears to be enough to cover
+ // the leftovers plus any inflation from scheduling-heavy build
+ // modes.
+ const threadsSlack = 5
// Make sure GC isn't running, since GC workers interfere with
// expected counts.
From c2fb15164bdb9d44a302771be613fbef5faa4a8e Mon Sep 17 00:00:00 2001
From: Sean Liao
Date: Sun, 5 Oct 2025 23:17:40 +0100
Subject: [PATCH 021/421] testing/synctest: remove Run
Run (experimental) is replaced by Test.
Fixes #74012
Change-Id: I1721e1edfbcb4f1fe2159dc0430a13685b2d08c4
Reviewed-on: https://go-review.googlesource.com/c/go/+/709355
Reviewed-by: Emmanuel Odeke
Reviewed-by: Damien Neil
Auto-Submit: Damien Neil
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Alan Donovan
---
src/testing/synctest/run.go | 16 ----------------
1 file changed, 16 deletions(-)
delete mode 100644 src/testing/synctest/run.go
diff --git a/src/testing/synctest/run.go b/src/testing/synctest/run.go
deleted file mode 100644
index 2e668ab8634..00000000000
--- a/src/testing/synctest/run.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// 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.
-
-//go:build goexperiment.synctest
-
-package synctest
-
-import "internal/synctest"
-
-// Run is deprecated.
-//
-// Deprecated: Use Test instead. Run will be removed in Go 1.26.
-func Run(f func()) {
- synctest.Run(f)
-}
From 24416458c21a48d83f34d3c2242d892e002bcd6c Mon Sep 17 00:00:00 2001
From: Ian Alexander
Date: Wed, 20 Aug 2025 19:12:20 -0400
Subject: [PATCH 022/421] cmd/go: export type State
Export the type `State` and add global variable `LoaderState` in
preparation for refactoring usage of other global variables in the
modload package.
This commit is part of the overall effort to eliminate global
modloader state.
[git-generate]
cd src/cmd/go/internal/modload
rf 'mv state State'
rf 'add State func NewState() *State { return &State{} }'
rf 'add init.go:/NewState/+0 var LoaderState = NewState()'
Change-Id: I0ec6199ba3e05927bec12f11a60383d1b51b111a
Reviewed-on: https://go-review.googlesource.com/c/go/+/698055
Reviewed-by: Michael Matloob
Reviewed-by: Michael Matloob
LUCI-TryBot-Result: Go LUCI
---
src/cmd/go/internal/modload/init.go | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 498ff7433ea..5192fe0fc8c 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -96,7 +96,7 @@ func EnterWorkspace(ctx context.Context) (exit func(), err error) {
}
// Reset the state to a clean state.
- oldstate := setState(state{})
+ oldstate := setState(State{})
ForceUseModules = true
// Load in workspace mode.
@@ -401,11 +401,11 @@ func WorkFilePath() string {
// Reset clears all the initialized, cached state about the use of modules,
// so that we can start over.
func Reset() {
- setState(state{})
+ setState(State{})
}
-func setState(s state) state {
- oldState := state{
+func setState(s State) State {
+ oldState := State{
initialized: initialized,
forceUseModules: ForceUseModules,
rootMode: RootMode,
@@ -429,7 +429,7 @@ func setState(s state) state {
return oldState
}
-type state struct {
+type State struct {
initialized bool
forceUseModules bool
rootMode Root
@@ -441,6 +441,10 @@ type state struct {
modfetchState modfetch.State
}
+func NewState() *State { return &State{} }
+
+var LoaderState = NewState()
+
// Init determines whether module mode is enabled, locates the root of the
// current module (if any), sets environment variables for Git subprocesses, and
// configures the cfg, codehost, load, modfetch, and search packages for use
From f3312124c2370c2f64a7f9ad29732ec30209647a Mon Sep 17 00:00:00 2001
From: Michael Pratt
Date: Mon, 6 Oct 2025 14:38:47 -0400
Subject: [PATCH 023/421] runtime: remove batching from spanSPMC free
Added in CL 700496, freeSomeSpanSPMCs attempts to bound tail latency by
processing at most 64 entries at a time, as well as returning early if
it notices a preemption request. Both of those are attempts to reduce
tail latency, as we cannot preempt the function while it holds the lock.
This scheme is based on a similar scheme in freeSomeWbufs.
freeSomeWbufs has a key difference: all workbufs in its list are
unconditionally freed. So freeSomeWbufs will always make forward
progress in each call (unless it is constantly preempted).
In contrast, freeSomeSpanSPMCs only frees "dead" entries. If the list
contains >64 live entries, a call may make no progress, and the caller
will simply keep calling in a loop forever, until the GC ends at which
point it returns success early. The infinite loop likely restarts at the
next GC cycle.
The queues are used on each P, so it is easy to have 64 permanently live
queues if GOMAXPROCS >= 64. If GOMAXPROCS < 64, it is possible to
transiently have more queues, but spanQueue.drain increases queue size
in an attempt to reach a steady state of one queue per P.
We must drop work.spanSPMCs.lock to allow preemption, but dropping the
lock allows mutation of the linked list, meaning we cannot simply
continue iteration after retaking lock. Since there is no
straightforward resolution to this and we expect this to generally only
be around 1 entry per P, simply remove the batching and process the
entire list without preemption. We may want to revisit this in the
future for very high GOMAXPROCS or if application regularly otherwise
create very long lists.
Fixes #75771.
Change-Id: I6a6a636cd3be443aacde5a678c460aa7066b4c4a
Reviewed-on: https://go-review.googlesource.com/c/go/+/709575
Reviewed-by: Michael Knyszek
LUCI-TryBot-Result: Go LUCI
---
src/runtime/mgc.go | 3 +--
src/runtime/mgcmark_greenteagc.go | 23 ++++++++++-------------
src/runtime/mgcmark_nogreenteagc.go | 4 ++--
src/runtime/mgcsweep.go | 5 +----
4 files changed, 14 insertions(+), 21 deletions(-)
diff --git a/src/runtime/mgc.go b/src/runtime/mgc.go
index b13ec845fc4..b4b4485629b 100644
--- a/src/runtime/mgc.go
+++ b/src/runtime/mgc.go
@@ -2046,8 +2046,7 @@ func gcSweep(mode gcMode) bool {
prepareFreeWorkbufs()
for freeSomeWbufs(false) {
}
- for freeSomeSpanSPMCs(false) {
- }
+ freeDeadSpanSPMCs()
// All "free" events for this mark/sweep cycle have
// now happened, so we can make this profile cycle
// available immediately.
diff --git a/src/runtime/mgcmark_greenteagc.go b/src/runtime/mgcmark_greenteagc.go
index 3975e1e76b5..7f8d60349ff 100644
--- a/src/runtime/mgcmark_greenteagc.go
+++ b/src/runtime/mgcmark_greenteagc.go
@@ -722,13 +722,8 @@ func (r *spanSPMC) slot(i uint32) *objptr {
return (*objptr)(unsafe.Add(unsafe.Pointer(r.ring), idx*unsafe.Sizeof(objptr(0))))
}
-// freeSomeSpanSPMCs frees some spanSPMCs back to the OS and returns
-// true if it should be called again to free more.
-func freeSomeSpanSPMCs(preemptible bool) bool {
- // TODO(mknyszek): This is arbitrary, but some kind of limit is necessary
- // to help bound delays to cooperatively preempt ourselves.
- const batchSize = 64
-
+// freeDeadSpanSPMCs frees dead spanSPMCs back to the OS.
+func freeDeadSpanSPMCs() {
// According to the SPMC memory management invariants, we can only free
// spanSPMCs outside of the mark phase. We ensure we do this in two ways.
//
@@ -740,18 +735,21 @@ func freeSomeSpanSPMCs(preemptible bool) bool {
//
// This way, we ensure that we don't start freeing if we're in the wrong
// phase, and the phase can't change on us while we're freeing.
+ //
+ // TODO(go.dev/issue/75771): Due to the grow semantics in
+ // spanQueue.drain, we expect a steady-state of around one spanSPMC per
+ // P, with some spikes higher when Ps have more than one. For high
+ // GOMAXPROCS, or if this list otherwise gets long, it would be nice to
+ // have a way to batch work that allows preemption during processing.
lock(&work.spanSPMCs.lock)
if gcphase != _GCoff || work.spanSPMCs.all == nil {
unlock(&work.spanSPMCs.lock)
- return false
+ return
}
rp := &work.spanSPMCs.all
- gp := getg()
- more := true
- for i := 0; i < batchSize && !(preemptible && gp.preempt); i++ {
+ for {
r := *rp
if r == nil {
- more = false
break
}
if r.dead.Load() {
@@ -766,7 +764,6 @@ func freeSomeSpanSPMCs(preemptible bool) bool {
}
}
unlock(&work.spanSPMCs.lock)
- return more
}
// tryStealSpan attempts to steal a span from another P's local queue.
diff --git a/src/runtime/mgcmark_nogreenteagc.go b/src/runtime/mgcmark_nogreenteagc.go
index 9838887f7be..883c3451abe 100644
--- a/src/runtime/mgcmark_nogreenteagc.go
+++ b/src/runtime/mgcmark_nogreenteagc.go
@@ -67,8 +67,8 @@ type spanSPMC struct {
_ sys.NotInHeap
}
-func freeSomeSpanSPMCs(preemptible bool) bool {
- return false
+func freeDeadSpanSPMCs() {
+ return
}
type objptr uintptr
diff --git a/src/runtime/mgcsweep.go b/src/runtime/mgcsweep.go
index 364cdb58ccb..c3d6afb90a5 100644
--- a/src/runtime/mgcsweep.go
+++ b/src/runtime/mgcsweep.go
@@ -307,10 +307,7 @@ func bgsweep(c chan int) {
// N.B. freeSomeWbufs is already batched internally.
goschedIfBusy()
}
- for freeSomeSpanSPMCs(true) {
- // N.B. freeSomeSpanSPMCs is already batched internally.
- goschedIfBusy()
- }
+ freeDeadSpanSPMCs()
lock(&sweep.lock)
if !isSweepDone() {
// This can happen if a GC runs between
From 719dfcf8a8478d70360bf3c34c0e920be7b32994 Mon Sep 17 00:00:00 2001
From: Keith Randall
Date: Sat, 17 May 2025 15:05:56 -0700
Subject: [PATCH 024/421] cmd/compile: redo arm64 LR/FP save and restore
Instead of storing LR (the return address) at 0(SP) and the FP
(parent's frame pointer) at -8(SP), store them at framesize-8(SP)
and framesize-16(SP), respectively.
We push and pop data onto the stack such that we're never accessing
anything below SP.
The prolog/epilog lengths are unchanged (3 insns for a typical prolog,
2 for a typical epilog).
We use 8 bytes more per frame.
Typical prologue:
STP.W (FP, LR), -16(SP)
MOVD SP, FP
SUB $C, SP
Typical epilogue:
ADD $C, SP
LDP.P 16(SP), (FP, LR)
RET
The previous word where we stored LR, at 0(SP), is now unused.
We could repurpose that slot for storing a local variable.
The new prolog and epilog instructions are recognized by libunwind,
so pc-sampling tools like perf should now be accurate. (TODO: except
maybe after the first RET instruction? Have to look into that.)
Update #73753 (fixes, for arm64)
Update #57302 (Quim thinks this will help on that issue)
Change-Id: I4800036a9a9a08aaaf35d9f99de79a36cf37ebb8
Reviewed-on: https://go-review.googlesource.com/c/go/+/674615
Reviewed-by: David Chase
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Keith Randall
---
src/cmd/compile/abi-internal.md | 12 +-
src/cmd/compile/internal/arm64/ggen.go | 10 +-
src/cmd/compile/internal/arm64/ssa.go | 2 +-
src/cmd/compile/internal/ssagen/pgen.go | 6 +
src/cmd/compile/internal/ssagen/ssa.go | 3 +-
src/cmd/internal/obj/arm64/asm7.go | 12 +-
src/cmd/internal/obj/arm64/obj7.go | 316 ++++++------------
src/cmd/link/internal/amd64/obj.go | 19 +-
src/cmd/link/internal/arm64/obj.go | 23 +-
src/cmd/link/internal/ld/dwarf.go | 7 +-
src/cmd/link/internal/ld/lib.go | 4 +
src/cmd/link/internal/ld/stackcheck.go | 5 -
src/cmd/link/internal/x86/obj.go | 15 +-
src/runtime/asm_arm64.s | 81 +++--
src/runtime/mkpreempt.go | 20 +-
src/runtime/panic.go | 8 +-
src/runtime/preempt_arm64.s | 15 +-
src/runtime/race_arm64.s | 17 +-
src/runtime/signal_arm64.go | 16 +-
src/runtime/stack.go | 20 +-
src/runtime/testdata/testprog/badtraceback.go | 5 +
src/runtime/traceback.go | 30 +-
test/nosplit.go | 8 +-
23 files changed, 298 insertions(+), 356 deletions(-)
diff --git a/src/cmd/compile/abi-internal.md b/src/cmd/compile/abi-internal.md
index eae230dc070..490e1affb74 100644
--- a/src/cmd/compile/abi-internal.md
+++ b/src/cmd/compile/abi-internal.md
@@ -576,19 +576,19 @@ A function's stack frame, after the frame is created, is laid out as
follows:
+------------------------------+
+ | return PC |
+ | frame pointer on entry | ← R29 points to
| ... locals ... |
| ... outgoing arguments ... |
- | return PC | ← RSP points to
- | frame pointer on entry |
+ | unused word | ← RSP points to
+------------------------------+ ↓ lower addresses
The "return PC" is loaded to the link register, R30, as part of the
arm64 `CALL` operation.
-On entry, a function subtracts from RSP to open its stack frame, and
-saves the values of R30 and R29 at the bottom of the frame.
-Specifically, R30 is saved at 0(RSP) and R29 is saved at -8(RSP),
-after RSP is updated.
+On entry, a function pushes R30 (the return address) and R29
+(the caller's frame pointer) onto the bottom of the stack. It then
+subtracts a constant from RSP to open its stack frame.
A leaf function that does not require any stack space may omit the
saved R30 and R29.
diff --git a/src/cmd/compile/internal/arm64/ggen.go b/src/cmd/compile/internal/arm64/ggen.go
index 14027467002..6ba56b992ee 100644
--- a/src/cmd/compile/internal/arm64/ggen.go
+++ b/src/cmd/compile/internal/arm64/ggen.go
@@ -11,10 +11,12 @@ import (
)
func padframe(frame int64) int64 {
- // arm64 requires that the frame size (not counting saved FP&LR)
- // be 16 bytes aligned. If not, pad it.
- if frame%16 != 0 {
- frame += 16 - (frame % 16)
+ // arm64 requires frame sizes here that are 8 mod 16.
+ // With the additional (unused) slot at the bottom of the frame,
+ // that makes an aligned 16 byte frame.
+ // Adding a save region for LR+FP does not change the alignment.
+ if frame != 0 {
+ frame += (-(frame + 8)) & 15
}
return frame
}
diff --git a/src/cmd/compile/internal/arm64/ssa.go b/src/cmd/compile/internal/arm64/ssa.go
index 7bc0e536e94..9f79a740c6c 100644
--- a/src/cmd/compile/internal/arm64/ssa.go
+++ b/src/cmd/compile/internal/arm64/ssa.go
@@ -221,7 +221,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
for i := 0; i < len(args); i++ {
a := args[i]
- // Offset by size of the saved LR slot.
+ // Offset by size of the unused slot before start of args.
addr := ssagen.SpillSlotAddr(a, arm64.REGSP, base.Ctxt.Arch.FixedFrameSize)
// Look for double-register operations if we can.
if i < len(args)-1 {
diff --git a/src/cmd/compile/internal/ssagen/pgen.go b/src/cmd/compile/internal/ssagen/pgen.go
index 0a2010363f8..f0776172b92 100644
--- a/src/cmd/compile/internal/ssagen/pgen.go
+++ b/src/cmd/compile/internal/ssagen/pgen.go
@@ -393,10 +393,16 @@ func StackOffset(slot ssa.LocalSlot) int32 {
case ir.PAUTO:
off = n.FrameOffset()
if base.Ctxt.Arch.FixedFrameSize == 0 {
+ // x86 return address
off -= int64(types.PtrSize)
}
if buildcfg.FramePointerEnabled {
+ // frame pointer
off -= int64(types.PtrSize)
+ if buildcfg.GOARCH == "arm64" {
+ // arm64 return address also
+ off -= int64(types.PtrSize)
+ }
}
}
return int32(off + slot.Off)
diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go
index 1e2159579df..107447f04cc 100644
--- a/src/cmd/compile/internal/ssagen/ssa.go
+++ b/src/cmd/compile/internal/ssagen/ssa.go
@@ -7150,6 +7150,7 @@ func defframe(s *State, e *ssafn, f *ssa.Func) {
// Insert code to zero ambiguously live variables so that the
// garbage collector only sees initialized values when it
// looks for pointers.
+ // Note: lo/hi are offsets from varp and will be negative.
var lo, hi int64
// Opaque state for backend to use. Current backends use it to
@@ -7157,7 +7158,7 @@ func defframe(s *State, e *ssafn, f *ssa.Func) {
var state uint32
// Iterate through declarations. Autos are sorted in decreasing
- // frame offset order.
+ // frame offset order (least negative to most negative).
for _, n := range e.curfn.Dcl {
if !n.Needzero() {
continue
diff --git a/src/cmd/internal/obj/arm64/asm7.go b/src/cmd/internal/obj/arm64/asm7.go
index 743d09a3190..281d705a3eb 100644
--- a/src/cmd/internal/obj/arm64/asm7.go
+++ b/src/cmd/internal/obj/arm64/asm7.go
@@ -51,7 +51,6 @@ type ctxt7 struct {
blitrl *obj.Prog
elitrl *obj.Prog
autosize int32
- extrasize int32
instoffset int64
pc int64
pool struct {
@@ -1122,8 +1121,7 @@ func span7(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
ctxt.Diag("arm64 ops not initialized, call arm64.buildop first")
}
- c := ctxt7{ctxt: ctxt, newprog: newprog, cursym: cursym, autosize: int32(p.To.Offset & 0xffffffff), extrasize: int32(p.To.Offset >> 32)}
- p.To.Offset &= 0xffffffff // extrasize is no longer needed
+ c := ctxt7{ctxt: ctxt, newprog: newprog, cursym: cursym, autosize: int32(p.To.Offset)}
// Process literal pool and allocate initial program counter for each Prog, before
// generating branch veneers.
@@ -2119,8 +2117,8 @@ func (c *ctxt7) aclass(a *obj.Addr) int {
// a.Offset is still relative to pseudo-SP.
a.Reg = obj.REG_NONE
}
- // The frame top 8 or 16 bytes are for FP
- c.instoffset = int64(c.autosize) + a.Offset - int64(c.extrasize)
+ // The frame top 16 bytes are for LR/FP
+ c.instoffset = int64(c.autosize) + a.Offset - extrasize
return autoclass(c.instoffset)
case obj.NAME_PARAM:
@@ -2180,8 +2178,8 @@ func (c *ctxt7) aclass(a *obj.Addr) int {
// a.Offset is still relative to pseudo-SP.
a.Reg = obj.REG_NONE
}
- // The frame top 8 or 16 bytes are for FP
- c.instoffset = int64(c.autosize) + a.Offset - int64(c.extrasize)
+ // The frame top 16 bytes are for LR/FP
+ c.instoffset = int64(c.autosize) + a.Offset - extrasize
case obj.NAME_PARAM:
if a.Reg == REGSP {
diff --git a/src/cmd/internal/obj/arm64/obj7.go b/src/cmd/internal/obj/arm64/obj7.go
index 2583e463542..a6974261451 100644
--- a/src/cmd/internal/obj/arm64/obj7.go
+++ b/src/cmd/internal/obj/arm64/obj7.go
@@ -36,7 +36,6 @@ import (
"cmd/internal/src"
"cmd/internal/sys"
"internal/abi"
- "internal/buildcfg"
"log"
"math"
)
@@ -472,6 +471,8 @@ func (c *ctxt7) rewriteToUseGot(p *obj.Prog) {
obj.Nopout(p)
}
+const extrasize = 16 // space needed in the frame for LR+FP
+
func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
if cursym.Func().Text == nil || cursym.Func().Text.Link == nil {
return
@@ -521,33 +522,26 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
c.autosize = int32(textstksiz)
if p.Mark&LEAF != 0 && c.autosize == 0 {
- // A leaf function with no locals has no frame.
+ // A leaf function with no locals needs no frame.
p.From.Sym.Set(obj.AttrNoFrame, true)
}
if !p.From.Sym.NoFrame() {
// If there is a stack frame at all, it includes
- // space to save the LR.
+ // space for the (now unused) word at [SP:SP+8].
c.autosize += 8
}
+ // Round up to a multiple of 16.
+ c.autosize += (-c.autosize) & 15
+
if c.autosize != 0 {
- extrasize := int32(0)
- if c.autosize%16 == 8 {
- // Allocate extra 8 bytes on the frame top to save FP
- extrasize = 8
- } else if c.autosize&(16-1) == 0 {
- // Allocate extra 16 bytes to save FP for the old frame whose size is 8 mod 16
- extrasize = 16
- } else {
- c.ctxt.Diag("%v: unaligned frame size %d - must be 16 aligned", p, c.autosize-8)
- }
+ // Allocate an extra 16 bytes at the top of the frame
+ // to save LR+FP.
c.autosize += extrasize
c.cursym.Func().Locals += extrasize
- // low 32 bits for autosize
- // high 32 bits for extrasize
- p.To.Offset = int64(c.autosize) | int64(extrasize)<<32
+ p.To.Offset = int64(c.autosize)
} else {
// NOFRAME
p.To.Offset = 0
@@ -580,120 +574,72 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
var prologueEnd *obj.Prog
aoffset := c.autosize
- if aoffset > 0xf0 {
- // MOVD.W offset variant range is -0x100 to 0xf8, SP should be 16-byte aligned.
- // so the maximum aoffset value is 0xf0.
- aoffset = 0xf0
+ if aoffset < 16 {
+ log.Fatalf("aoffset too small %d", aoffset)
}
- // Frame is non-empty. Make sure to save link register, even if
- // it is a leaf function, so that traceback works.
q = p
- if c.autosize > aoffset {
- // Frame size is too large for a MOVD.W instruction. Store the frame pointer
- // register and link register before decrementing SP, so if a signal comes
- // during the execution of the function prologue, the traceback code will
- // not see a half-updated stack frame.
- // SUB $autosize, RSP, R20
- q1 = obj.Appendp(q, c.newprog)
- q1.Pos = p.Pos
- q1.As = ASUB
- q1.From.Type = obj.TYPE_CONST
- q1.From.Offset = int64(c.autosize)
- q1.Reg = REGSP
- q1.To.Type = obj.TYPE_REG
- q1.To.Reg = REG_R20
+ // Store return address and frame pointer at the top of the stack frame.
+ // STP.W (R29, R30), -16(SP)
+ q1 = obj.Appendp(q, c.newprog)
+ q1.Pos = p.Pos
+ q1.As = ASTP
+ q1.From.Type = obj.TYPE_REGREG
+ q1.From.Reg = REGFP
+ q1.From.Offset = REGLINK
+ q1.To.Type = obj.TYPE_MEM
+ q1.To.Reg = REG_RSP
+ q1.To.Offset = -16
+ q1.Scond = C_XPRE
- prologueEnd = q1
+ prologueEnd = q1
- // STP (R29, R30), -8(R20)
- q1 = obj.Appendp(q1, c.newprog)
- q1.Pos = p.Pos
- q1.As = ASTP
- q1.From.Type = obj.TYPE_REGREG
- q1.From.Reg = REGFP
- q1.From.Offset = REGLINK
- q1.To.Type = obj.TYPE_MEM
- q1.To.Reg = REG_R20
- q1.To.Offset = -8
+ // Update frame pointer
+ q1 = obj.Appendp(q1, c.newprog)
+ q1.Pos = p.Pos
+ q1.As = AMOVD
+ q1.From.Type = obj.TYPE_REG
+ q1.From.Reg = REGSP
+ q1.To.Type = obj.TYPE_REG
+ q1.To.Reg = REGFP
- // This is not async preemptible, as if we open a frame
- // at the current SP, it will clobber the saved LR.
- q1 = c.ctxt.StartUnsafePoint(q1, c.newprog)
-
- // MOVD R20, RSP
- q1 = obj.Appendp(q1, c.newprog)
- q1.Pos = p.Pos
- q1.As = AMOVD
- q1.From.Type = obj.TYPE_REG
- q1.From.Reg = REG_R20
- q1.To.Type = obj.TYPE_REG
- q1.To.Reg = REGSP
- q1.Spadj = c.autosize
-
- q1 = c.ctxt.EndUnsafePoint(q1, c.newprog, -1)
-
- if buildcfg.GOOS == "ios" {
- // iOS does not support SA_ONSTACK. We will run the signal handler
- // on the G stack. If we write below SP, it may be clobbered by
- // the signal handler. So we save FP and LR after decrementing SP.
- // STP (R29, R30), -8(RSP)
+ // Allocate additional frame space.
+ adj := aoffset - 16
+ if adj > 0 {
+ // SUB $autosize-16, RSP
+ if adj < 1<<12 {
q1 = obj.Appendp(q1, c.newprog)
q1.Pos = p.Pos
- q1.As = ASTP
- q1.From.Type = obj.TYPE_REGREG
- q1.From.Reg = REGFP
- q1.From.Offset = REGLINK
- q1.To.Type = obj.TYPE_MEM
+ q1.As = ASUB
+ q1.From.Type = obj.TYPE_CONST
+ q1.From.Offset = int64(adj)
+ q1.To.Type = obj.TYPE_REG
+ q1.To.Reg = REGSP
+ } else {
+ // Constant too big for atomic subtract.
+ // Materialize in tmp register first.
+ q1 = obj.Appendp(q1, c.newprog)
+ q1.Pos = p.Pos
+ q1.As = AMOVD
+ q1.From.Type = obj.TYPE_CONST
+ q1.From.Offset = int64(adj)
+ q1.To.Type = obj.TYPE_REG
+ q1.To.Reg = REGTMP
+
+ q1 = obj.Appendp(q1, c.newprog)
+ q1.Pos = p.Pos
+ q1.As = ASUB
+ q1.From.Type = obj.TYPE_REG
+ q1.From.Reg = REGTMP
+ q1.To.Type = obj.TYPE_REG
q1.To.Reg = REGSP
- q1.To.Offset = -8
}
- } else {
- // small frame, update SP and save LR in a single MOVD.W instruction.
- // So if a signal comes during the execution of the function prologue,
- // the traceback code will not see a half-updated stack frame.
- // Also, on Linux, in a cgo binary we may get a SIGSETXID signal
- // early on before the signal stack is set, as glibc doesn't allow
- // us to block SIGSETXID. So it is important that we don't write below
- // the SP until the signal stack is set.
- // Luckily, all the functions from thread entry to setting the signal
- // stack have small frames.
- q1 = obj.Appendp(q, c.newprog)
- q1.As = AMOVD
- q1.Pos = p.Pos
- q1.From.Type = obj.TYPE_REG
- q1.From.Reg = REGLINK
- q1.To.Type = obj.TYPE_MEM
- q1.Scond = C_XPRE
- q1.To.Offset = int64(-aoffset)
- q1.To.Reg = REGSP
- q1.Spadj = aoffset
-
- prologueEnd = q1
-
- // Frame pointer.
- q1 = obj.Appendp(q1, c.newprog)
- q1.Pos = p.Pos
- q1.As = AMOVD
- q1.From.Type = obj.TYPE_REG
- q1.From.Reg = REGFP
- q1.To.Type = obj.TYPE_MEM
- q1.To.Reg = REGSP
- q1.To.Offset = -8
+ q1.Spadj = adj
}
prologueEnd.Pos = prologueEnd.Pos.WithXlogue(src.PosPrologueEnd)
- q1 = obj.Appendp(q1, c.newprog)
- q1.Pos = p.Pos
- q1.As = ASUB
- q1.From.Type = obj.TYPE_CONST
- q1.From.Offset = 8
- q1.Reg = REGSP
- q1.To.Type = obj.TYPE_REG
- q1.To.Reg = REGFP
-
case obj.ARET:
nocache(p)
if p.From.Type == obj.TYPE_CONST {
@@ -707,105 +653,56 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
}
p.To = obj.Addr{}
aoffset := c.autosize
- if c.cursym.Func().Text.Mark&LEAF != 0 {
- if aoffset != 0 {
- // Restore frame pointer.
- // ADD $framesize-8, RSP, R29
- p.As = AADD
- p.From.Type = obj.TYPE_CONST
- p.From.Offset = int64(c.autosize) - 8
- p.Reg = REGSP
- p.To.Type = obj.TYPE_REG
- p.To.Reg = REGFP
-
- // Pop stack frame.
- // ADD $framesize, RSP, RSP
- p = obj.Appendp(p, c.newprog)
- p.As = AADD
- p.From.Type = obj.TYPE_CONST
- p.From.Offset = int64(c.autosize)
- p.To.Type = obj.TYPE_REG
- p.To.Reg = REGSP
- p.Spadj = -c.autosize
+ if aoffset > 0 {
+ if aoffset < 16 {
+ log.Fatalf("aoffset too small %d", aoffset)
}
- } else if aoffset <= 0xF0 {
- // small frame, restore LR and update SP in a single MOVD.P instruction.
- // There is no correctness issue to use a single LDP for LR and FP,
- // but the instructions are not pattern matched with the prologue's
- // MOVD.W and MOVD, which may cause performance issue in
- // store-forwarding.
+ adj := aoffset - 16
+ if adj > 0 {
+ if adj < 1<<12 {
+ // ADD $adj, RSP, RSP
+ p.As = AADD
+ p.From.Type = obj.TYPE_CONST
+ p.From.Offset = int64(adj)
+ p.To.Type = obj.TYPE_REG
+ p.To.Reg = REGSP
+ } else {
+ // Put frame size in a separate register and
+ // add it in with a single instruction,
+ // so we never have a partial frame during
+ // the epilog. See issue 73259.
- // MOVD -8(RSP), R29
- p.As = AMOVD
- p.From.Type = obj.TYPE_MEM
- p.From.Reg = REGSP
- p.From.Offset = -8
- p.To.Type = obj.TYPE_REG
- p.To.Reg = REGFP
- p = obj.Appendp(p, c.newprog)
+ // MOVD $adj, REGTMP
+ p.As = AMOVD
+ p.From.Type = obj.TYPE_CONST
+ p.From.Offset = int64(adj)
+ p.To.Type = obj.TYPE_REG
+ p.To.Reg = REGTMP
+ // ADD REGTMP, RSP, RSP
+ p = obj.Appendp(p, c.newprog)
+ p.As = AADD
+ p.From.Type = obj.TYPE_REG
+ p.From.Reg = REGTMP
+ p.To.Type = obj.TYPE_REG
+ p.To.Reg = REGSP
+ }
+ p.Spadj = -adj
+ }
- // MOVD.P offset(RSP), R30
- p.As = AMOVD
- p.From.Type = obj.TYPE_MEM
- p.Scond = C_XPOST
- p.From.Offset = int64(aoffset)
- p.From.Reg = REGSP
- p.To.Type = obj.TYPE_REG
- p.To.Reg = REGLINK
- p.Spadj = -aoffset
- } else {
- // LDP -8(RSP), (R29, R30)
+ // Pop LR+FP.
+ // LDP.P 16(RSP), (R29, R30)
+ if p.As != obj.ARET {
+ p = obj.Appendp(p, c.newprog)
+ }
p.As = ALDP
p.From.Type = obj.TYPE_MEM
- p.From.Offset = -8
p.From.Reg = REGSP
+ p.From.Offset = 16
+ p.Scond = C_XPOST
p.To.Type = obj.TYPE_REGREG
p.To.Reg = REGFP
p.To.Offset = REGLINK
-
- if aoffset < 1<<12 {
- // ADD $aoffset, RSP, RSP
- q = newprog()
- q.As = AADD
- q.From.Type = obj.TYPE_CONST
- q.From.Offset = int64(aoffset)
- q.To.Type = obj.TYPE_REG
- q.To.Reg = REGSP
- q.Spadj = -aoffset
- q.Pos = p.Pos
- q.Link = p.Link
- p.Link = q
- p = q
- } else {
- // Put frame size in a separate register and
- // add it in with a single instruction,
- // so we never have a partial frame during
- // the epilog. See issue 73259.
-
- // MOVD $aoffset, REGTMP
- q = newprog()
- q.As = AMOVD
- q.From.Type = obj.TYPE_CONST
- q.From.Offset = int64(aoffset)
- q.To.Type = obj.TYPE_REG
- q.To.Reg = REGTMP
- q.Pos = p.Pos
- q.Link = p.Link
- p.Link = q
- p = q
- // ADD REGTMP, RSP, RSP
- q = newprog()
- q.As = AADD
- q.From.Type = obj.TYPE_REG
- q.From.Reg = REGTMP
- q.To.Type = obj.TYPE_REG
- q.To.Reg = REGSP
- q.Spadj = -aoffset
- q.Pos = p.Pos
- q.Link = p.Link
- p.Link = q
- p = q
- }
+ p.Spadj = -16
}
// If enabled, this code emits 'MOV PC, R27' before every 'MOV LR, PC',
@@ -868,10 +765,11 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
p.From.Type = obj.TYPE_REG
p.From.Reg = REGLINK
} else {
- /* MOVD (RSP), Rd */
+ /* MOVD framesize-8(RSP), Rd */
p.As = AMOVD
p.From.Type = obj.TYPE_MEM
p.From.Reg = REGSP
+ p.From.Offset = int64(c.autosize - 8)
}
}
if p.To.Type == obj.TYPE_REG && p.To.Reg == REGSP && p.Spadj == 0 {
@@ -906,6 +804,12 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
p.From.Reg = int16(REG_LSL + r + (shift&7)<<5)
p.From.Offset = 0
}
+ if p.To.Type == obj.TYPE_MEM && p.To.Reg == REG_RSP && (p.Scond == C_XPRE || p.Scond == C_XPOST) {
+ p.Spadj += int32(-p.To.Offset)
+ }
+ if p.From.Type == obj.TYPE_MEM && p.From.Reg == REG_RSP && (p.Scond == C_XPRE || p.Scond == C_XPOST) {
+ p.Spadj += int32(-p.From.Offset)
+ }
}
}
diff --git a/src/cmd/link/internal/amd64/obj.go b/src/cmd/link/internal/amd64/obj.go
index 3a6141b9091..761496549f9 100644
--- a/src/cmd/link/internal/amd64/obj.go
+++ b/src/cmd/link/internal/amd64/obj.go
@@ -51,15 +51,16 @@ func Init() (*sys.Arch, ld.Arch) {
Plan9Magic: uint32(4*26*26 + 7),
Plan9_64Bit: true,
- Adddynrel: adddynrel,
- Archinit: archinit,
- Archreloc: archreloc,
- Archrelocvariant: archrelocvariant,
- Gentext: gentext,
- Machoreloc1: machoreloc1,
- MachorelocSize: 8,
- PEreloc1: pereloc1,
- TLSIEtoLE: tlsIEtoLE,
+ Adddynrel: adddynrel,
+ Archinit: archinit,
+ Archreloc: archreloc,
+ Archrelocvariant: archrelocvariant,
+ Gentext: gentext,
+ Machoreloc1: machoreloc1,
+ MachorelocSize: 8,
+ PEreloc1: pereloc1,
+ TLSIEtoLE: tlsIEtoLE,
+ ReturnAddressAtTopOfFrame: true,
ELF: ld.ELFArch{
Linuxdynld: "/lib64/ld-linux-x86-64.so.2",
diff --git a/src/cmd/link/internal/arm64/obj.go b/src/cmd/link/internal/arm64/obj.go
index 3d358155bad..e1e4ade8183 100644
--- a/src/cmd/link/internal/arm64/obj.go
+++ b/src/cmd/link/internal/arm64/obj.go
@@ -47,17 +47,18 @@ func Init() (*sys.Arch, ld.Arch) {
Dwarfreglr: dwarfRegLR,
TrampLimit: 0x7c00000, // 26-bit signed offset * 4, leave room for PLT etc.
- Adddynrel: adddynrel,
- Archinit: archinit,
- Archreloc: archreloc,
- Archrelocvariant: archrelocvariant,
- Extreloc: extreloc,
- Gentext: gentext,
- GenSymsLate: gensymlate,
- Machoreloc1: machoreloc1,
- MachorelocSize: 8,
- PEreloc1: pereloc1,
- Trampoline: trampoline,
+ Adddynrel: adddynrel,
+ Archinit: archinit,
+ Archreloc: archreloc,
+ Archrelocvariant: archrelocvariant,
+ Extreloc: extreloc,
+ Gentext: gentext,
+ GenSymsLate: gensymlate,
+ Machoreloc1: machoreloc1,
+ MachorelocSize: 8,
+ PEreloc1: pereloc1,
+ Trampoline: trampoline,
+ ReturnAddressAtTopOfFrame: true,
ELF: ld.ELFArch{
Androiddynld: "/system/bin/linker64",
diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go
index 0003938ef2e..c4d12a5488d 100644
--- a/src/cmd/link/internal/ld/dwarf.go
+++ b/src/cmd/link/internal/ld/dwarf.go
@@ -1544,9 +1544,14 @@ func (d *dwctxt) writeframes(fs loader.Sym) dwarfSecInfo {
if pcsp.Value > 0 {
// The return address is preserved at (CFA-frame_size)
// after a stack frame has been allocated.
+ off := -spdelta
+ if thearch.ReturnAddressAtTopOfFrame {
+ // Except arm64, which has it at the top of frame.
+ off = -int64(d.arch.PtrSize)
+ }
deltaBuf = append(deltaBuf, dwarf.DW_CFA_offset_extended_sf)
deltaBuf = dwarf.AppendUleb128(deltaBuf, uint64(thearch.Dwarfreglr))
- deltaBuf = dwarf.AppendSleb128(deltaBuf, -spdelta/dataAlignmentFactor)
+ deltaBuf = dwarf.AppendSleb128(deltaBuf, off/dataAlignmentFactor)
} else {
// The return address is restored into the link register
// when a stack frame has been de-allocated.
diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
index 2c861129b52..5f5ebfc1d98 100644
--- a/src/cmd/link/internal/ld/lib.go
+++ b/src/cmd/link/internal/ld/lib.go
@@ -263,6 +263,10 @@ type Arch struct {
// optional override for assignAddress
AssignAddress func(ldr *loader.Loader, sect *sym.Section, n int, s loader.Sym, va uint64, isTramp bool) (*sym.Section, int, uint64)
+ // Reports whether the return address is stored at the top (highest address)
+ // of the stack frame.
+ ReturnAddressAtTopOfFrame bool
+
// ELF specific information.
ELF ELFArch
}
diff --git a/src/cmd/link/internal/ld/stackcheck.go b/src/cmd/link/internal/ld/stackcheck.go
index 98e7edaeb14..14cd3a22384 100644
--- a/src/cmd/link/internal/ld/stackcheck.go
+++ b/src/cmd/link/internal/ld/stackcheck.go
@@ -9,7 +9,6 @@ import (
"cmd/internal/objabi"
"cmd/link/internal/loader"
"fmt"
- "internal/buildcfg"
"sort"
"strings"
)
@@ -62,10 +61,6 @@ func (ctxt *Link) doStackCheck() {
// that there are at least StackLimit bytes available below SP
// when morestack returns.
limit := objabi.StackNosplit(*flagRace) - sc.callSize
- if buildcfg.GOARCH == "arm64" {
- // Need an extra 8 bytes below SP to save FP.
- limit -= 8
- }
// Compute stack heights without any back-tracking information.
// This will almost certainly succeed and we can simply
diff --git a/src/cmd/link/internal/x86/obj.go b/src/cmd/link/internal/x86/obj.go
index 4336f01ea3d..a4885fde8fd 100644
--- a/src/cmd/link/internal/x86/obj.go
+++ b/src/cmd/link/internal/x86/obj.go
@@ -50,13 +50,14 @@ func Init() (*sys.Arch, ld.Arch) {
Plan9Magic: uint32(4*11*11 + 7),
- Adddynrel: adddynrel,
- Archinit: archinit,
- Archreloc: archreloc,
- Archrelocvariant: archrelocvariant,
- Gentext: gentext,
- Machoreloc1: machoreloc1,
- PEreloc1: pereloc1,
+ Adddynrel: adddynrel,
+ Archinit: archinit,
+ Archreloc: archreloc,
+ Archrelocvariant: archrelocvariant,
+ Gentext: gentext,
+ Machoreloc1: machoreloc1,
+ PEreloc1: pereloc1,
+ ReturnAddressAtTopOfFrame: true,
ELF: ld.ELFArch{
Linuxdynld: "/lib/ld-linux.so.2",
diff --git a/src/runtime/asm_arm64.s b/src/runtime/asm_arm64.s
index a0e82ec830f..aa49a27a75d 100644
--- a/src/runtime/asm_arm64.s
+++ b/src/runtime/asm_arm64.s
@@ -50,9 +50,7 @@ TEXT _rt0_arm64_lib(SB),NOSPLIT,$184
CBZ R4, nocgo
MOVD $_rt0_arm64_lib_go(SB), R0
MOVD $0, R1
- SUB $16, RSP // reserve 16 bytes for sp-8 where fp may be saved.
BL (R4)
- ADD $16, RSP
B restore
nocgo:
@@ -371,7 +369,6 @@ switch:
BL runtime·save_g(SB)
MOVD (g_sched+gobuf_sp)(g), R0
MOVD R0, RSP
- MOVD (g_sched+gobuf_bp)(g), R29
MOVD $0, (g_sched+gobuf_sp)(g)
MOVD $0, (g_sched+gobuf_bp)(g)
RET
@@ -381,8 +378,8 @@ noswitch:
// Using a tail call here cleans up tracebacks since we won't stop
// at an intermediate systemstack.
MOVD 0(R26), R3 // code pointer
- MOVD.P 16(RSP), R30 // restore LR
- SUB $8, RSP, R29 // restore FP
+ ADD $16, RSP
+ LDP.P 16(RSP), (R29,R30) // restore FP, LR
B (R3)
// func switchToCrashStack0(fn func())
@@ -1051,7 +1048,7 @@ again:
// Smashes R0.
TEXT gosave_systemstack_switch<>(SB),NOSPLIT|NOFRAME,$0
MOVD $runtime·systemstack_switch(SB), R0
- ADD $8, R0 // get past prologue
+ ADD $12, R0 // get past prologue
MOVD R0, (g_sched+gobuf_pc)(g)
MOVD RSP, R0
MOVD R0, (g_sched+gobuf_sp)(g)
@@ -1069,9 +1066,7 @@ TEXT gosave_systemstack_switch<>(SB),NOSPLIT|NOFRAME,$0
TEXT ·asmcgocall_no_g(SB),NOSPLIT,$0-16
MOVD fn+0(FP), R1
MOVD arg+8(FP), R0
- SUB $16, RSP // skip over saved frame pointer below RSP
BL (R1)
- ADD $16, RSP // skip over saved frame pointer below RSP
RET
// func asmcgocall(fn, arg unsafe.Pointer) int32
@@ -1236,9 +1231,9 @@ havem:
BL runtime·save_g(SB)
MOVD (g_sched+gobuf_sp)(g), R4 // prepare stack as R4
MOVD (g_sched+gobuf_pc)(g), R5
- MOVD R5, -48(R4)
+ MOVD R5, -8(R4)
MOVD (g_sched+gobuf_bp)(g), R5
- MOVD R5, -56(R4)
+ MOVD R5, -16(R4)
// Gather our arguments into registers.
MOVD fn+0(FP), R1
MOVD frame+8(FP), R2
@@ -1252,7 +1247,7 @@ havem:
CALL (R0) // indirect call to bypass nosplit check. We're on a different stack now.
// Restore g->sched (== m->curg->sched) from saved values.
- MOVD 0(RSP), R5
+ MOVD 40(RSP), R5
MOVD R5, (g_sched+gobuf_pc)(g)
MOVD RSP, R4
ADD $48, R4, R4
@@ -1490,10 +1485,57 @@ GLOBL debugCallFrameTooLarge<>(SB), RODATA, $20 // Size duplicated below
//
// This is ABIInternal because Go code injects its PC directly into new
// goroutine stacks.
+//
+// State before debugger starts doing anything:
+// | current |
+// | stack |
+// +-------------+ <- SP = origSP
+// stopped executing at PC = origPC
+// some values are in LR (origLR) and FP (origFP)
+//
+// After debugger has done steps 1-6 above:
+// | current |
+// | stack |
+// +-------------+ <- origSP
+// | ----- | (used to be a slot to store frame pointer on entry to origPC's frame.)
+// +-------------+
+// | origLR |
+// +-------------+ <- SP
+// | ----- |
+// +-------------+
+// | argsize |
+// +-------------+
+// LR = origPC, PC = debugCallV2
+//
+// debugCallV2 then modifies the stack up to the "good" label:
+// | current |
+// | stack |
+// +-------------+ <- origSP
+// | ----- | (used to be a slot to store frame pointer on entry to origPC's frame.)
+// +-------------+
+// | origLR |
+// +-------------+ <- where debugger left SP
+// | origPC |
+// +-------------+
+// | origFP |
+// +-------------+ <- FP = SP + 256
+// | saved |
+// | registers |
+// | (224 bytes) |
+// +-------------+ <- SP + 32
+// | space for |
+// | outargs |
+// +-------------+ <- SP + 8
+// | argsize |
+// +-------------+ <- SP
+
TEXT runtime·debugCallV2(SB),NOSPLIT|NOFRAME,$0-0
- STP (R29, R30), -280(RSP)
- SUB $272, RSP, RSP
- SUB $8, RSP, R29
+ MOVD R30, -8(RSP) // save origPC
+ MOVD -16(RSP), R30 // save argsize in R30 temporarily
+ MOVD.W R29, -16(RSP) // push origFP
+ MOVD RSP, R29 // frame pointer chain now set up
+ SUB $256, RSP, RSP // allocate frame
+ MOVD R30, (RSP) // Save argsize on the stack
// Save all registers that may contain pointers so they can be
// conservatively scanned.
//
@@ -1515,7 +1557,8 @@ TEXT runtime·debugCallV2(SB),NOSPLIT|NOFRAME,$0-0
STP (R0, R1), (4*8)(RSP)
// Perform a safe-point check.
- MOVD R30, 8(RSP) // Caller's PC
+ MOVD 264(RSP), R0 // origPC
+ MOVD R0, 8(RSP)
CALL runtime·debugCallCheck(SB)
MOVD 16(RSP), R0
CBZ R0, good
@@ -1559,7 +1602,7 @@ good:
CALL runtime·debugCallWrap(SB); \
JMP restore
- MOVD 256(RSP), R0 // the argument frame size
+ MOVD (RSP), R0 // the argument frame size
DEBUG_CALL_DISPATCH(debugCall32<>, 32)
DEBUG_CALL_DISPATCH(debugCall64<>, 64)
DEBUG_CALL_DISPATCH(debugCall128<>, 128)
@@ -1607,9 +1650,9 @@ restore:
LDP (6*8)(RSP), (R2, R3)
LDP (4*8)(RSP), (R0, R1)
- LDP -8(RSP), (R29, R27)
- ADD $288, RSP, RSP // Add 16 more bytes, see saveSigContext
- MOVD -16(RSP), R30 // restore old lr
+ MOVD 272(RSP), R30 // restore old lr (saved by (*sigctxt).pushCall)
+ LDP 256(RSP), (R29, R27) // restore old fp, set up resumption address
+ ADD $288, RSP, RSP // Pop frame, LR+FP, and block pushed by (*sigctxt).pushCall
JMP (R27)
// runtime.debugCallCheck assumes that functions defined with the
diff --git a/src/runtime/mkpreempt.go b/src/runtime/mkpreempt.go
index 769c4ffc5c9..9064cae039f 100644
--- a/src/runtime/mkpreempt.go
+++ b/src/runtime/mkpreempt.go
@@ -488,26 +488,18 @@ func genARM64(g *gen) {
l.stack += 8 // SP needs 16-byte alignment
}
- // allocate frame, save PC of interrupted instruction (in LR)
- p("MOVD R30, %d(RSP)", -l.stack)
+ // allocate frame, save PC (in R30), FP (in R29) of interrupted instruction
+ p("STP.W (R29, R30), -16(RSP)")
+ p("MOVD RSP, R29") // set up new frame pointer
p("SUB $%d, RSP", l.stack)
- p("MOVD R29, -8(RSP)") // save frame pointer (only used on Linux)
- p("SUB $8, RSP, R29") // set up new frame pointer
- // On iOS, save the LR again after decrementing SP. We run the
- // signal handler on the G stack (as it doesn't support sigaltstack),
- // so any writes below SP may be clobbered.
- p("#ifdef GOOS_ios")
- p("MOVD R30, (RSP)")
- p("#endif")
l.save(g)
p("CALL ·asyncPreempt2(SB)")
l.restore(g)
- p("MOVD %d(RSP), R30", l.stack) // sigctxt.pushCall has pushed LR (at interrupt) on stack, restore it
- p("MOVD -8(RSP), R29") // restore frame pointer
- p("MOVD (RSP), R27") // load PC to REGTMP
- p("ADD $%d, RSP", l.stack+16) // pop frame (including the space pushed by sigctxt.pushCall)
+ p("MOVD %d(RSP), R30", l.stack+16) // sigctxt.pushCall has pushed LR (at interrupt) on stack, restore it
+ p("LDP %d(RSP), (R29, R27)", l.stack) // Restore frame pointer. Load PC into regtmp.
+ p("ADD $%d, RSP", l.stack+32) // pop frame (including the space pushed by sigctxt.pushCall)
p("RET (R27)")
}
diff --git a/src/runtime/panic.go b/src/runtime/panic.go
index 8c91c9435ab..04b3afe1682 100644
--- a/src/runtime/panic.go
+++ b/src/runtime/panic.go
@@ -1379,10 +1379,10 @@ func recovery(gp *g) {
// the caller
gp.sched.bp = fp - 2*goarch.PtrSize
case goarch.IsArm64 != 0:
- // on arm64, the architectural bp points one word higher
- // than the sp. fp is totally useless to us here, because it
- // only gets us to the caller's fp.
- gp.sched.bp = sp - goarch.PtrSize
+ // on arm64, the first two words of the frame are caller's PC
+ // (the saved LR register) and the caller's BP.
+ // Coincidentally, the same as amd64.
+ gp.sched.bp = fp - 2*goarch.PtrSize
}
gogo(&gp.sched)
}
diff --git a/src/runtime/preempt_arm64.s b/src/runtime/preempt_arm64.s
index 31ec9d940f7..f4248cac257 100644
--- a/src/runtime/preempt_arm64.s
+++ b/src/runtime/preempt_arm64.s
@@ -4,13 +4,9 @@
#include "textflag.h"
TEXT ·asyncPreempt(SB),NOSPLIT|NOFRAME,$0-0
- MOVD R30, -496(RSP)
+ STP.W (R29, R30), -16(RSP)
+ MOVD RSP, R29
SUB $496, RSP
- MOVD R29, -8(RSP)
- SUB $8, RSP, R29
- #ifdef GOOS_ios
- MOVD R30, (RSP)
- #endif
STP (R0, R1), 8(RSP)
STP (R2, R3), 24(RSP)
STP (R4, R5), 40(RSP)
@@ -78,8 +74,7 @@ TEXT ·asyncPreempt(SB),NOSPLIT|NOFRAME,$0-0
LDP 40(RSP), (R4, R5)
LDP 24(RSP), (R2, R3)
LDP 8(RSP), (R0, R1)
- MOVD 496(RSP), R30
- MOVD -8(RSP), R29
- MOVD (RSP), R27
- ADD $512, RSP
+ MOVD 512(RSP), R30
+ LDP 496(RSP), (R29, R27)
+ ADD $528, RSP
RET (R27)
diff --git a/src/runtime/race_arm64.s b/src/runtime/race_arm64.s
index 5df650105bb..feaa328d4c0 100644
--- a/src/runtime/race_arm64.s
+++ b/src/runtime/race_arm64.s
@@ -397,7 +397,7 @@ TEXT racecallatomic<>(SB), NOSPLIT, $0
// R3 = addr of incoming arg list
// Trigger SIGSEGV early.
- MOVD 40(RSP), R3 // 1st arg is addr. after two times BL, get it at 40(RSP)
+ MOVD 72(RSP), R3 // 1st arg is addr. after two small frames (32 bytes each), get it at 72(RSP)
MOVB (R3), R13 // segv here if addr is bad
// Check that addr is within [arenastart, arenaend) or within [racedatastart, racedataend).
MOVD runtime·racearenastart(SB), R10
@@ -417,10 +417,11 @@ racecallatomic_ok:
// Addr is within the good range, call the atomic function.
load_g
MOVD g_racectx(g), R0 // goroutine context
- MOVD 16(RSP), R1 // caller pc
+ MOVD 56(RSP), R1 // caller pc
MOVD R9, R2 // pc
- ADD $40, RSP, R3
- JMP racecall<>(SB) // does not return
+ ADD $72, RSP, R3
+ BL racecall<>(SB)
+ RET
racecallatomic_ignore:
// Addr is outside the good range.
// Call __tsan_go_ignore_sync_begin to ignore synchronization during the atomic op.
@@ -435,9 +436,9 @@ racecallatomic_ignore:
// racecall will call LLVM race code which might clobber R28 (g)
load_g
MOVD g_racectx(g), R0 // goroutine context
- MOVD 16(RSP), R1 // caller pc
+ MOVD 56(RSP), R1 // caller pc
MOVD R9, R2 // pc
- ADD $40, RSP, R3 // arguments
+ ADD $72, RSP, R3 // arguments
BL racecall<>(SB)
// Call __tsan_go_ignore_sync_end.
MOVD $__tsan_go_ignore_sync_end(SB), R9
@@ -476,10 +477,6 @@ TEXT racecall<>(SB), NOSPLIT|NOFRAME, $0-0
MOVD (g_sched+gobuf_sp)(R11), R12
MOVD R12, RSP
call:
- // Decrement SP past where the frame pointer is saved in the Go arm64
- // ABI (one word below the stack pointer) so the race detector library
- // code doesn't clobber it
- SUB $16, RSP
BL R9
MOVD R19, RSP
JMP (R20)
diff --git a/src/runtime/signal_arm64.go b/src/runtime/signal_arm64.go
index af7d29f9de1..61dad507219 100644
--- a/src/runtime/signal_arm64.go
+++ b/src/runtime/signal_arm64.go
@@ -8,7 +8,6 @@ package runtime
import (
"internal/abi"
- "internal/goarch"
"internal/runtime/sys"
"unsafe"
)
@@ -63,18 +62,11 @@ func (c *sigctxt) preparePanic(sig uint32, gp *g) {
// We arrange lr, and pc to pretend the panicking
// function calls sigpanic directly.
// Always save LR to stack so that panics in leaf
- // functions are correctly handled. This smashes
- // the stack frame but we're not going back there
- // anyway.
+ // functions are correctly handled.
+ // This extra space is known to gentraceback.
sp := c.sp() - sys.StackAlign // needs only sizeof uint64, but must align the stack
c.set_sp(sp)
*(*uint64)(unsafe.Pointer(uintptr(sp))) = c.lr()
- // Make sure a valid frame pointer is saved on the stack so that the
- // frame pointer checks in adjustframe are happy, if they're enabled.
- // Frame pointer unwinding won't visit the sigpanic frame, since
- // sigpanic will save the same frame pointer before calling into a panic
- // function.
- *(*uint64)(unsafe.Pointer(uintptr(sp - goarch.PtrSize))) = c.r29()
pc := gp.sigpc
@@ -96,10 +88,6 @@ func (c *sigctxt) pushCall(targetPC, resumePC uintptr) {
sp := c.sp() - 16 // SP needs 16-byte alignment
c.set_sp(sp)
*(*uint64)(unsafe.Pointer(uintptr(sp))) = c.lr()
- // Make sure a valid frame pointer is saved on the stack so that the
- // frame pointer checks in adjustframe are happy, if they're enabled.
- // This is not actually used for unwinding.
- *(*uint64)(unsafe.Pointer(uintptr(sp - goarch.PtrSize))) = c.r29()
// Set up PC and LR to pretend the function being signaled
// calls targetPC at resumePC.
c.set_lr(uint64(resumePC))
diff --git a/src/runtime/stack.go b/src/runtime/stack.go
index 55e97e77afa..5eaceec6da1 100644
--- a/src/runtime/stack.go
+++ b/src/runtime/stack.go
@@ -579,23 +579,27 @@ var ptrnames = []string{
// | args to callee |
// +------------------+ <- frame->sp
//
-// (arm)
+// (arm64)
// +------------------+
// | args from caller |
// +------------------+ <- frame->argp
-// | caller's retaddr |
+// | |
+// +------------------+ <- frame->fp (aka caller's sp)
+// | return address |
// +------------------+
-// | caller's FP (*) | (*) on ARM64, if framepointer_enabled && varp > sp
+// | caller's FP | (frame pointer always enabled: TODO)
// +------------------+ <- frame->varp
// | locals |
// +------------------+
// | args to callee |
// +------------------+
-// | return address |
+// | |
// +------------------+ <- frame->sp
//
// varp > sp means that the function has a frame;
// varp == sp means frameless function.
+//
+// Alignment padding, if needed, will be between "locals" and "args to callee".
type adjustinfo struct {
old stack
@@ -709,7 +713,8 @@ func adjustframe(frame *stkframe, adjinfo *adjustinfo) {
}
// Adjust saved frame pointer if there is one.
- if (goarch.ArchFamily == goarch.AMD64 || goarch.ArchFamily == goarch.ARM64) && frame.argp-frame.varp == 2*goarch.PtrSize {
+ if goarch.ArchFamily == goarch.AMD64 && frame.argp-frame.varp == 2*goarch.PtrSize ||
+ goarch.ArchFamily == goarch.ARM64 && frame.argp-frame.varp == 3*goarch.PtrSize {
if stackDebug >= 3 {
print(" saved bp\n")
}
@@ -723,10 +728,7 @@ func adjustframe(frame *stkframe, adjinfo *adjustinfo) {
throw("bad frame pointer")
}
}
- // On AMD64, this is the caller's frame pointer saved in the current
- // frame.
- // On ARM64, this is the frame pointer of the caller's caller saved
- // by the caller in its frame (one word below its SP).
+ // This is the caller's frame pointer saved in the current frame.
adjustpointer(adjinfo, unsafe.Pointer(frame.varp))
}
diff --git a/src/runtime/testdata/testprog/badtraceback.go b/src/runtime/testdata/testprog/badtraceback.go
index 455118a5437..36575f765db 100644
--- a/src/runtime/testdata/testprog/badtraceback.go
+++ b/src/runtime/testdata/testprog/badtraceback.go
@@ -41,6 +41,11 @@ func badLR2(arg int) {
if runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" {
lrOff = 32 // FIXED_FRAME or sys.MinFrameSize
}
+ if runtime.GOARCH == "arm64" {
+ // skip 8 bytes at bottom of parent frame, then point
+ // to the 8 bytes of the saved PC at the top of the frame.
+ lrOff = 16
+ }
lrPtr := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&arg)) - lrOff))
*lrPtr = 0xbad
diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go
index 8882c306edb..1c3e679a02b 100644
--- a/src/runtime/traceback.go
+++ b/src/runtime/traceback.go
@@ -175,6 +175,11 @@ func (u *unwinder) initAt(pc0, sp0, lr0 uintptr, gp *g, flags unwindFlags) {
// Start in the caller's frame.
if frame.pc == 0 {
if usesLR {
+ // TODO: this isn't right on arm64. But also, this should
+ // ~never happen. Calling a nil function will panic
+ // when loading the PC out of the closure, not when
+ // branching to that PC. (Closures should always have
+ // valid PCs in their first word.)
frame.pc = *(*uintptr)(unsafe.Pointer(frame.sp))
frame.lr = 0
} else {
@@ -369,7 +374,11 @@ func (u *unwinder) resolveInternal(innermost, isSyscall bool) {
var lrPtr uintptr
if usesLR {
if innermost && frame.sp < frame.fp || frame.lr == 0 {
- lrPtr = frame.sp
+ if GOARCH == "arm64" {
+ lrPtr = frame.fp - goarch.PtrSize
+ } else {
+ lrPtr = frame.sp
+ }
frame.lr = *(*uintptr)(unsafe.Pointer(lrPtr))
}
} else {
@@ -385,24 +394,17 @@ func (u *unwinder) resolveInternal(innermost, isSyscall bool) {
// On x86, call instruction pushes return PC before entering new function.
frame.varp -= goarch.PtrSize
}
+ if GOARCH == "arm64" && frame.varp > frame.sp {
+ frame.varp -= goarch.PtrSize // LR have been saved, skip over it.
+ }
// For architectures with frame pointers, if there's
// a frame, then there's a saved frame pointer here.
//
// NOTE: This code is not as general as it looks.
- // On x86, the ABI is to save the frame pointer word at the
+ // On x86 and arm64, the ABI is to save the frame pointer word at the
// top of the stack frame, so we have to back down over it.
- // On arm64, the frame pointer should be at the bottom of
- // the stack (with R29 (aka FP) = RSP), in which case we would
- // not want to do the subtraction here. But we started out without
- // any frame pointer, and when we wanted to add it, we didn't
- // want to break all the assembly doing direct writes to 8(RSP)
- // to set the first parameter to a called function.
- // So we decided to write the FP link *below* the stack pointer
- // (with R29 = RSP - 8 in Go functions).
- // This is technically ABI-compatible but not standard.
- // And it happens to end up mimicking the x86 layout.
- // Other architectures may make different decisions.
+ // No other architectures are framepointer-enabled at the moment.
if frame.varp > frame.sp && framepointer_enabled {
frame.varp -= goarch.PtrSize
}
@@ -562,7 +564,7 @@ func (u *unwinder) finishInternal() {
gp := u.g.ptr()
if u.flags&(unwindPrintErrors|unwindSilentErrors) == 0 && u.frame.sp != gp.stktopsp {
print("runtime: g", gp.goid, ": frame.sp=", hex(u.frame.sp), " top=", hex(gp.stktopsp), "\n")
- print("\tstack=[", hex(gp.stack.lo), "-", hex(gp.stack.hi), "\n")
+ print("\tstack=[", hex(gp.stack.lo), "-", hex(gp.stack.hi), "]\n")
throw("traceback did not unwind completely")
}
}
diff --git a/test/nosplit.go b/test/nosplit.go
index 4b4c93b1d06..1f943fa18c3 100644
--- a/test/nosplit.go
+++ b/test/nosplit.go
@@ -142,7 +142,7 @@ start 136
# (CallSize is 32 on ppc64, 8 on amd64 for frame pointer.)
start 96 nosplit
start 100 nosplit; REJECT ppc64 ppc64le
-start 104 nosplit; REJECT ppc64 ppc64le arm64
+start 104 nosplit; REJECT ppc64 ppc64le
start 108 nosplit; REJECT ppc64 ppc64le
start 112 nosplit; REJECT ppc64 ppc64le arm64
start 116 nosplit; REJECT ppc64 ppc64le
@@ -160,7 +160,7 @@ start 136 nosplit; REJECT
# Because AMD64 uses frame pointer, it has 8 fewer bytes.
start 96 nosplit call f; f 0 nosplit
start 100 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
-start 104 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le arm64
+start 104 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
start 108 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
start 112 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 arm64
start 116 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
@@ -176,7 +176,7 @@ start 136 nosplit call f; f 0 nosplit; REJECT
# Architectures differ in the same way as before.
start 96 nosplit call f; f 0 call f
start 100 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
-start 104 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64
+start 104 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
start 108 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
start 112 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64
start 116 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
@@ -189,7 +189,7 @@ start 136 nosplit call f; f 0 call f; REJECT
# Indirect calls are assumed to be splitting functions.
start 96 nosplit callind
start 100 nosplit callind; REJECT ppc64 ppc64le
-start 104 nosplit callind; REJECT ppc64 ppc64le amd64 arm64
+start 104 nosplit callind; REJECT ppc64 ppc64le amd64
start 108 nosplit callind; REJECT ppc64 ppc64le amd64
start 112 nosplit callind; REJECT ppc64 ppc64le amd64 arm64
start 116 nosplit callind; REJECT ppc64 ppc64le amd64
From 4fca79833fcdd0dc19bb0feba8715a0def3d07be Mon Sep 17 00:00:00 2001
From: tony
Date: Mon, 29 Sep 2025 14:25:57 +0000
Subject: [PATCH 025/421] runtime: delete redundant code in the page allocator
The page allocator's scavenge index has sysGrow called on it twice,
once in pageAlloc.grow, and once in pageAlloc.sysGrow on 64-bit
platforms. Calling it twice is OK since sysGrow is idempotent,
but it's also wasteful. This change removes the call
in pageAlloc.sysGrow.
Change-Id: I5b955b6e2beed5c2b8305ab82b76718ea305792c
Reviewed-on: https://go-review.googlesource.com/c/go/+/707735
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Michael Knyszek
Reviewed-by: Carlos Amedee
---
src/runtime/mpagealloc_64bit.go | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/runtime/mpagealloc_64bit.go b/src/runtime/mpagealloc_64bit.go
index eb425f07044..2e3643004bc 100644
--- a/src/runtime/mpagealloc_64bit.go
+++ b/src/runtime/mpagealloc_64bit.go
@@ -180,9 +180,6 @@ func (p *pageAlloc) sysGrow(base, limit uintptr) {
sysUsed(unsafe.Pointer(need.base.addr()), need.size(), need.size())
p.summaryMappedReady += need.size()
}
-
- // Update the scavenge index.
- p.summaryMappedReady += p.scav.index.sysGrow(base, limit, p.sysStat)
}
// sysGrow increases the index's backing store in response to a heap growth.
From 1d62e92567a858b18f4e7e0c24e071c039dd3edf Mon Sep 17 00:00:00 2001
From: Cherry Mui
Date: Sat, 4 Oct 2025 11:32:33 -0400
Subject: [PATCH 026/421] test/codegen: make sure assignment results are used.
Some tests make assignments to an argument without reading it.
With CL 708865, they are treated as dead stores and are removed.
Make sure the results are used.
Fixes #75745.
Fixes #75746.
Change-Id: I05580beb1006505ec1550e5fa245b54dcefd10b9
Reviewed-on: https://go-review.googlesource.com/c/go/+/708916
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Keith Randall
Reviewed-by: Keith Randall
---
test/codegen/constants.go | 6 ++++--
test/codegen/mathbits.go | 3 ++-
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/test/codegen/constants.go b/test/codegen/constants.go
index 3ce17d0ad3a..0935a9e53a9 100644
--- a/test/codegen/constants.go
+++ b/test/codegen/constants.go
@@ -7,7 +7,7 @@
package codegen
// A uint16 or sint16 constant shifted left.
-func shifted16BitConstants(out [64]uint64) {
+func shifted16BitConstants() (out [64]uint64) {
// ppc64x: "MOVD\t[$]8193,", "SLD\t[$]27,"
out[0] = 0x0000010008000000
// ppc64x: "MOVD\t[$]-32767", "SLD\t[$]26,"
@@ -16,10 +16,11 @@ func shifted16BitConstants(out [64]uint64) {
out[2] = 0xFFFF000000000000
// ppc64x: "MOVD\t[$]65535", "SLD\t[$]44,"
out[3] = 0x0FFFF00000000000
+ return
}
// A contiguous set of 1 bits, potentially wrapping.
-func contiguousMaskConstants(out [64]uint64) {
+func contiguousMaskConstants() (out [64]uint64) {
// ppc64x: "MOVD\t[$]-1", "RLDC\tR[0-9]+, [$]44, [$]63,"
out[0] = 0xFFFFF00000000001
// ppc64x: "MOVD\t[$]-1", "RLDC\tR[0-9]+, [$]43, [$]63,"
@@ -30,4 +31,5 @@ func contiguousMaskConstants(out [64]uint64) {
// ppc64x/power9: "MOVD\t[$]-1", "RLDC\tR[0-9]+, [$]33, [$]63,"
// ppc64x/power10: "MOVD\t[$]-8589934591,"
out[3] = 0xFFFFFFFE00000001
+ return
}
diff --git a/test/codegen/mathbits.go b/test/codegen/mathbits.go
index ba5387d2c32..f8fa374c0af 100644
--- a/test/codegen/mathbits.go
+++ b/test/codegen/mathbits.go
@@ -731,7 +731,7 @@ func Add64MPanicOnOverflowGT(a, b [2]uint64) [2]uint64 {
//
// This is what happened on PPC64 when compiling
// crypto/internal/edwards25519/field.feMulGeneric.
-func Add64MultipleChains(a, b, c, d [2]uint64) {
+func Add64MultipleChains(a, b, c, d [2]uint64) [2]uint64 {
var cx, d1, d2 uint64
a1, a2 := a[0], a[1]
b1, b2 := b[0], b[1]
@@ -748,6 +748,7 @@ func Add64MultipleChains(a, b, c, d [2]uint64) {
d2, _ = bits.Add64(c2, d2, cx)
d[0] = d1
d[1] = d2
+ return d
}
// --------------- //
From 7bfeb43509acbe75ce8e1a14c60bffe597e46813 Mon Sep 17 00:00:00 2001
From: Ian Alexander
Date: Wed, 20 Aug 2025 19:16:36 -0400
Subject: [PATCH 027/421] cmd/go: refactor usage of `initialized`
This commit refactors usage of the global variable `initialized` to
the global LoaderState field of the same name.
This commit is part of the overall effort to eliminate global
modloader state.
[git-generate]
cd src/cmd/go/internal/modload
rf 'ex { initialized -> LoaderState.initialized }'
rf 'rm initialized'
Change-Id: I97e35bab00f4c22661670b01b69425fc25efe6df
Reviewed-on: https://go-review.googlesource.com/c/go/+/698056
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Michael Matloob
Reviewed-by: Michael Matloob
---
src/cmd/go/internal/modload/init.go | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 5192fe0fc8c..d7d532ec942 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -60,7 +60,6 @@ var (
// Variables set in Init.
var (
- initialized bool
// These are primarily used to initialize the MainModules, and should be
// eventually superseded by them but are still used in cases where the module
@@ -406,7 +405,7 @@ func Reset() {
func setState(s State) State {
oldState := State{
- initialized: initialized,
+ initialized: LoaderState.initialized,
forceUseModules: ForceUseModules,
rootMode: RootMode,
modRoots: modRoots,
@@ -414,7 +413,7 @@ func setState(s State) State {
mainModules: MainModules,
requirements: requirements,
}
- initialized = s.initialized
+ LoaderState.initialized = s.initialized
ForceUseModules = s.forceUseModules
RootMode = s.rootMode
modRoots = s.modRoots
@@ -450,10 +449,10 @@ var LoaderState = NewState()
// configures the cfg, codehost, load, modfetch, and search packages for use
// with modules.
func Init() {
- if initialized {
+ if LoaderState.initialized {
return
}
- initialized = true
+ LoaderState.initialized = true
fips140.Init()
@@ -573,7 +572,7 @@ func WillBeEnabled() bool {
// Already enabled.
return true
}
- if initialized {
+ if LoaderState.initialized {
// Initialized, not enabled.
return false
}
@@ -640,7 +639,7 @@ func VendorDir() string {
}
func inWorkspaceMode() bool {
- if !initialized {
+ if !LoaderState.initialized {
panic("inWorkspaceMode called before modload.Init called")
}
if !Enabled() {
@@ -1253,7 +1252,7 @@ func fixVersion(ctx context.Context, fixed *bool) modfile.VersionFixer {
// This function affects the default cfg.BuildMod when outside of a module,
// so it can only be called prior to Init.
func AllowMissingModuleImports() {
- if initialized {
+ if LoaderState.initialized {
panic("AllowMissingModuleImports after Init")
}
allowMissingModuleImports = true
From 7fbf54bfebf9243550177bc6871d80e58bedf1a6 Mon Sep 17 00:00:00 2001
From: Michael Anthony Knyszek
Date: Sun, 9 Mar 2025 17:19:48 +0000
Subject: [PATCH 028/421] internal/buildcfg: enable greenteagc experiment by
default
Slightly bump the value in Test/wasmmemsize.go. We use 1 additional page
compared to before, and we were already sitting *right* on the edge.
For #73581.
Change-Id: I485df16c3cf59803a8a1fc852b3e90666981ab09
Reviewed-on: https://go-review.googlesource.com/c/go/+/656195
LUCI-TryBot-Result: Go LUCI
Auto-Submit: Michael Knyszek
Reviewed-by: Cherry Mui
---
src/internal/buildcfg/exp.go | 6 ++++++
test/wasmmemsize.dir/main.go | 12 +++++++-----
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/src/internal/buildcfg/exp.go b/src/internal/buildcfg/exp.go
index d06913d9a7f..fb6b5859e31 100644
--- a/src/internal/buildcfg/exp.go
+++ b/src/internal/buildcfg/exp.go
@@ -78,12 +78,18 @@ func ParseGOEXPERIMENT(goos, goarch, goexp string) (*ExperimentFlags, error) {
// things like .debug_addr (needed for DWARF 5).
dwarf5Supported := (goos != "darwin" && goos != "ios" && goos != "aix")
+ // The compiler crashes while compiling some of the Green Tea code.
+ // The Green Tea code is pretty normal, so this is likely a compiler
+ // bug in the loong64 port.
+ greenTeaGCSupported := goarch != "loong64"
+
baseline := goexperiment.Flags{
RegabiWrappers: regabiSupported,
RegabiArgs: regabiSupported,
Dwarf5: dwarf5Supported,
RandomizedHeapBase64: true,
SizeSpecializedMalloc: true,
+ GreenTeaGC: greenTeaGCSupported,
}
// Start with the statically enabled set of experiments.
diff --git a/test/wasmmemsize.dir/main.go b/test/wasmmemsize.dir/main.go
index e3aa5b5e921..c51e6b3b047 100644
--- a/test/wasmmemsize.dir/main.go
+++ b/test/wasmmemsize.dir/main.go
@@ -9,17 +9,19 @@ import (
"io"
)
-// Expect less than 3 MB of memory usage for a small wasm program.
-// This reflects the current allocator. If the allocator changes,
-// update this value.
-const want = 3 << 20
+// Wasm page size.
+const pageSize = 64 * 1024
+
+// Expect less than 3 MB + 1 page of memory usage for a small wasm
+// program. This reflects the current allocator. If the allocator
+// changes, update this value.
+const want = 3<<20 + pageSize
var w = io.Discard
func main() {
fmt.Fprintln(w, "hello world")
- const pageSize = 64 * 1024
sz := uintptr(currentMemory()) * pageSize
if sz > want {
fmt.Printf("FAIL: unexpected memory size %d, want <= %d\n", sz, want)
From c1e6e49d5d3f3fb927f1bfd1b453d8e7c906c6ac Mon Sep 17 00:00:00 2001
From: thepudds
Date: Fri, 3 Oct 2025 10:59:54 -0400
Subject: [PATCH 029/421] fmt: reduce Errorf("x") allocations to match
errors.New("x")
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
For unformatted strings, it comes up periodically that there are
more allocations using fmt.Errorf("x") compared to errors.New("x").
People cite it as a reason to switch code using fmt.Errorf to
use errors.New instead.
Three examples from the last few weeks essentially made
this suggestion: #75235, CL 708496, and CL 708618. Prior to that,
it is periodically suggested as a vet check (e.g., proposals #17173
and #52696) or in various CLs to change the standard library
(e.g., CL 403938 and CL 588776).
On the other hand, I believe the position of the core Go team
is that it is usually not worthwhile to make such a change. For example,
in #52696, Russ wrote:
Thanks for raising the issue, but please don't do this. Using
fmt.Errorf("foo") is completely fine, especially in a program where
all the errors are constructed with fmt.Errorf. Having to
mentally switch between two functions based on the argument
is unnecessary noise.
This CL attempts to mostly take performance out of the discussion.
We drop from 2 allocations to 0 allocations for a non-escaping error,
and drop from 2 allocations to 1 allocation for an escaping error:
_ = fmt.Errorf("foo") // non-escaping error
sink = fmt.Errorf("foo") // escaping error
This now matches the allocations for errors.New("foo") in both cases.
The CPU cost difference is greatly reduced, though there is still
a small ~4ns difference measurable in these microbenchmarks. Previously,
it was ~64ns vs. ~21ns for fmt.Errorf("x") vs. errors.New("x")
for escaping errors, whereas with this CL it is now ~25ns vs. ~21ns.
When fmt.Errorf("foo") executes with this CL, there are essentially
three optimizations now, in rough order of usefulness:
(1) we always avoid an allocation inside the doPrintf machinery;
(2) if the error does not otherwise escape, we can stack allocate
the errors.errorString struct by virtue of mid-stack inlining
of fmt.Errorf and the resulting inlining of errors.New, which
also can be more effective via PGO;
(3) stringslite.IndexByte is a tiny bit faster than going through the
for loops looking for '%' inside doPrintf.
See https://blog.filippo.io/efficient-go-apis-with-the-inliner/ for
background on avoiding heap allocations via mid-stack inlining.
The common case here is likely that the string format argument is a
constant when there are no other arguments.
However, one concern could be that by not allocating a copy, we could
now keep a string argument alive longer with this change, which could
be a pessimization if for example that string argument is a
slice of a much bigger string:
s := bigString[m:n]
longLivedErr := fmt.Errorf(s)
Aside from that being perhaps unusual code, vet will complain about
s there as a "non-constant format string in call to fmt.Errorf", so that
particular example seems unlikely to occur frequently in practice.
The main benchmark results are below. "old" is prior to this CL, "new"
is with this CL. The non-escaping case is "local", the escaping case is
"sink". In practice, I suspect errors escape the majority of the time.
Benchmark code at https://go.dev/play/p/rlRSO1ehx8O
goos: linux
goarch: amd64
pkg: fmt
cpu: AMD EPYC 7B13
│ old-7bd6fac4.txt │ new-dcd2a72f0.txt │
│ sec/op │ sec/op vs base │
Errorf/no-args/local-16 63.76n ± 1% 4.874n ± 0% -92.36% (n=120)
Errorf/no-args/sink-16 64.25n ± 1% 25.81n ± 0% -59.83% (n=120)
Errorf/int-arg/local-16 90.86n ± 1% 90.97n ± 1% ~ (p=0.713 n=120)
Errorf/int-arg/sink-16 91.81n ± 1% 91.10n ± 1% -0.76% (p=0.036 n=120)
geomean 76.46n 31.95n -58.20%
│ old-7bd6fac4.txt │ new-dcd2a72f0.txt │
│ B/op │ B/op vs base │
Errorf/no-args/local-16 19.00 ± 0% 0.00 ± 0% -100.00% (n=120)
Errorf/no-args/sink-16 19.00 ± 0% 16.00 ± 0% -15.79% (n=120)
Errorf/int-arg/local-16 24.00 ± 0% 24.00 ± 0% ~ (p=1.000 n=120) ¹
Errorf/int-arg/sink-16 24.00 ± 0% 24.00 ± 0% ~ (p=1.000 n=120) ¹
geomean 21.35 ? ² ³
¹ all samples are equal
│ old-7bd6fac4.txt │ new-dcd2a72f0.txt │
│ allocs/op │ allocs/op vs base │
Errorf/no-args/local-16 2.000 ± 0% 0.000 ± 0% -100.00% (n=120)
Errorf/no-args/sink-16 2.000 ± 0% 1.000 ± 0% -50.00% (n=120)
Errorf/int-arg/local-16 2.000 ± 0% 2.000 ± 0% ~ (p=1.000 n=120) ¹
Errorf/int-arg/sink-16 2.000 ± 0% 2.000 ± 0% ~ (p=1.000 n=120) ¹
geomean 2.000 ? ² ³
¹ all samples are equal
Change-Id: Ib27c52933bec5c2236624c577fbb1741052e792f
Reviewed-on: https://go-review.googlesource.com/c/go/+/708836
Reviewed-by: Damien Neil
LUCI-TryBot-Result: Go LUCI
Commit-Queue: t hepudds
Reviewed-by: Alan Donovan
Reviewed-by: Emmanuel Odeke
---
src/fmt/errors.go | 18 +++++++++++++++++-
src/fmt/errors_test.go | 9 +++++++++
src/fmt/fmt_test.go | 5 +++++
3 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/src/fmt/errors.go b/src/fmt/errors.go
index 1ac83404bc7..a0ce7ada346 100644
--- a/src/fmt/errors.go
+++ b/src/fmt/errors.go
@@ -6,6 +6,7 @@ package fmt
import (
"errors"
+ "internal/stringslite"
"slices"
)
@@ -19,7 +20,22 @@ import (
// order they appear in the arguments.
// It is invalid to supply the %w verb with an operand that does not implement
// the error interface. The %w verb is otherwise a synonym for %v.
-func Errorf(format string, a ...any) error {
+func Errorf(format string, a ...any) (err error) {
+ // This function has been split in a somewhat unnatural way
+ // so that both it and the errors.New call can be inlined.
+ if err = errorf(format, a...); err != nil {
+ return err
+ }
+ // No formatting was needed. We can avoid some allocations and other work.
+ // See https://go.dev/cl/708836 for details.
+ return errors.New(format)
+}
+
+// errorf formats and returns an error value, or nil if no formatting is required.
+func errorf(format string, a ...any) error {
+ if len(a) == 0 && stringslite.IndexByte(format, '%') == -1 {
+ return nil
+ }
p := newPrinter()
p.wrapErrs = true
p.doPrintf(format, a)
diff --git a/src/fmt/errors_test.go b/src/fmt/errors_test.go
index 4eb55faffe7..52bf42d0a62 100644
--- a/src/fmt/errors_test.go
+++ b/src/fmt/errors_test.go
@@ -54,6 +54,15 @@ func TestErrorf(t *testing.T) {
}, {
err: noVetErrorf("%w is not an error", "not-an-error"),
wantText: "%!w(string=not-an-error) is not an error",
+ }, {
+ err: fmt.Errorf("no verbs"),
+ wantText: "no verbs",
+ }, {
+ err: noVetErrorf("no verbs with extra arg", "extra"),
+ wantText: "no verbs with extra arg%!(EXTRA string=extra)",
+ }, {
+ err: noVetErrorf("too many verbs: %w %v"),
+ wantText: "too many verbs: %!w(MISSING) %!v(MISSING)",
}, {
err: noVetErrorf("wrapped two errors: %w %w", errString("1"), errString("2")),
wantText: "wrapped two errors: 1 2",
diff --git a/src/fmt/fmt_test.go b/src/fmt/fmt_test.go
index 86e458ae648..c07da5683c2 100644
--- a/src/fmt/fmt_test.go
+++ b/src/fmt/fmt_test.go
@@ -1480,6 +1480,7 @@ func BenchmarkFprintIntNoAlloc(b *testing.B) {
var mallocBuf bytes.Buffer
var mallocPointer *int // A pointer so we know the interface value won't allocate.
+var sink any
var mallocTest = []struct {
count int
@@ -1510,6 +1511,10 @@ var mallocTest = []struct {
mallocBuf.Reset()
Fprintf(&mallocBuf, "%x %x %x", mallocPointer, mallocPointer, mallocPointer)
}},
+ {0, `Errorf("hello")`, func() { _ = Errorf("hello") }},
+ {2, `Errorf("hello: %x")`, func() { _ = Errorf("hello: %x", mallocPointer) }},
+ {1, `sink = Errorf("hello")`, func() { sink = Errorf("hello") }},
+ {2, `sink = Errorf("hello: %x")`, func() { sink = Errorf("hello: %x", mallocPointer) }},
}
var _ bytes.Buffer
From 4c0fd3a2b45675a581ef6fa273a221d7131b5647 Mon Sep 17 00:00:00 2001
From: Mateusz Poliwczak
Date: Mon, 6 Oct 2025 20:54:27 +0200
Subject: [PATCH 030/421] internal/goexperiment: remove the synctest
GOEXPERIMENT
synctest package is enabled by default and the synctest
goexperiment does nothing after CL 709355.
Change-Id: Ia96b070d5f3779ae7c38a9044f754e716a6a6964
Reviewed-on: https://go-review.googlesource.com/c/go/+/709555
Reviewed-by: Michael Pratt
Reviewed-by: Damien Neil
LUCI-TryBot-Result: Go LUCI
---
src/internal/goexperiment/exp_synctest_off.go | 8 --------
src/internal/goexperiment/exp_synctest_on.go | 8 --------
src/internal/goexperiment/flags.go | 3 ---
3 files changed, 19 deletions(-)
delete mode 100644 src/internal/goexperiment/exp_synctest_off.go
delete mode 100644 src/internal/goexperiment/exp_synctest_on.go
diff --git a/src/internal/goexperiment/exp_synctest_off.go b/src/internal/goexperiment/exp_synctest_off.go
deleted file mode 100644
index fade13f89ca..00000000000
--- a/src/internal/goexperiment/exp_synctest_off.go
+++ /dev/null
@@ -1,8 +0,0 @@
-// Code generated by mkconsts.go. DO NOT EDIT.
-
-//go:build !goexperiment.synctest
-
-package goexperiment
-
-const Synctest = false
-const SynctestInt = 0
diff --git a/src/internal/goexperiment/exp_synctest_on.go b/src/internal/goexperiment/exp_synctest_on.go
deleted file mode 100644
index 9c44be72761..00000000000
--- a/src/internal/goexperiment/exp_synctest_on.go
+++ /dev/null
@@ -1,8 +0,0 @@
-// Code generated by mkconsts.go. DO NOT EDIT.
-
-//go:build goexperiment.synctest
-
-package goexperiment
-
-const Synctest = true
-const SynctestInt = 1
diff --git a/src/internal/goexperiment/flags.go b/src/internal/goexperiment/flags.go
index 232a17135d2..07aa1d0aeed 100644
--- a/src/internal/goexperiment/flags.go
+++ b/src/internal/goexperiment/flags.go
@@ -100,9 +100,6 @@ type Flags struct {
// inlining phase within the Go compiler.
NewInliner bool
- // Synctest enables the testing/synctest package.
- Synctest bool
-
// Dwarf5 enables DWARF version 5 debug info generation.
Dwarf5 bool
From 64699542031b994ec4fdb6de887a94b69a372f9b Mon Sep 17 00:00:00 2001
From: Michael Pratt
Date: Mon, 6 Oct 2025 16:38:29 -0400
Subject: [PATCH 031/421] runtime: assert p.destroy runs with GC not running
This is already guaranteed by stopTheWorldGC prior to procresize. Thus
the cleanup code here is dead, which is a bit confusing.
Replace it with a throw for clarity.
Change-Id: I6a6a636c8ca1487b720c4fab41b2b86c13d1d9e0
Reviewed-on: https://go-review.googlesource.com/c/go/+/709655
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Michael Knyszek
---
src/runtime/proc.go | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/runtime/proc.go b/src/runtime/proc.go
index e5686705293..d36895b046c 100644
--- a/src/runtime/proc.go
+++ b/src/runtime/proc.go
@@ -5818,11 +5818,13 @@ func (pp *p) destroy() {
// Move all timers to the local P.
getg().m.p.ptr().timers.take(&pp.timers)
- // Flush p's write barrier buffer.
- if gcphase != _GCoff {
- wbBufFlush1(pp)
- pp.gcw.dispose()
+ // No need to flush p's write barrier buffer or span queue, as Ps
+ // cannot be destroyed during the mark phase.
+ if phase := gcphase; phase != _GCoff {
+ println("runtime: p id", pp.id, "destroyed during GC phase", phase)
+ throw("P destroyed while GC is running")
}
+
clear(pp.sudogbuf[:])
pp.sudogcache = pp.sudogbuf[:0]
pp.pinnerCache = nil
From c938051dd0b80a5c60572d6807270d06ca685d2e Mon Sep 17 00:00:00 2001
From: Keith Randall
Date: Tue, 7 Oct 2025 07:58:50 -0700
Subject: [PATCH 032/421] Revert "cmd/compile: redo arm64 LR/FP save and
restore"
This reverts commit 719dfcf8a8478d70360bf3c34c0e920be7b32994.
Reason for revert: Causing crashes.
Change-Id: I0b8526dd03d82fa074ce4f97f1789eeac702b3eb
Reviewed-on: https://go-review.googlesource.com/c/go/+/709755
Reviewed-by: Keith Randall
Reviewed-by: David Chase
LUCI-TryBot-Result: Go LUCI
Auto-Submit: Keith Randall
Reviewed-by: Cherry Mui
---
src/cmd/compile/abi-internal.md | 12 +-
src/cmd/compile/internal/arm64/ggen.go | 10 +-
src/cmd/compile/internal/arm64/ssa.go | 2 +-
src/cmd/compile/internal/ssagen/pgen.go | 6 -
src/cmd/compile/internal/ssagen/ssa.go | 3 +-
src/cmd/internal/obj/arm64/asm7.go | 12 +-
src/cmd/internal/obj/arm64/obj7.go | 316 ++++++++++++------
src/cmd/link/internal/amd64/obj.go | 19 +-
src/cmd/link/internal/arm64/obj.go | 23 +-
src/cmd/link/internal/ld/dwarf.go | 7 +-
src/cmd/link/internal/ld/lib.go | 4 -
src/cmd/link/internal/ld/stackcheck.go | 5 +
src/cmd/link/internal/x86/obj.go | 15 +-
src/runtime/asm_arm64.s | 81 ++---
src/runtime/mkpreempt.go | 20 +-
src/runtime/panic.go | 8 +-
src/runtime/preempt_arm64.s | 15 +-
src/runtime/race_arm64.s | 17 +-
src/runtime/signal_arm64.go | 16 +-
src/runtime/stack.go | 20 +-
src/runtime/testdata/testprog/badtraceback.go | 5 -
src/runtime/traceback.go | 30 +-
test/nosplit.go | 8 +-
23 files changed, 356 insertions(+), 298 deletions(-)
diff --git a/src/cmd/compile/abi-internal.md b/src/cmd/compile/abi-internal.md
index 490e1affb74..eae230dc070 100644
--- a/src/cmd/compile/abi-internal.md
+++ b/src/cmd/compile/abi-internal.md
@@ -576,19 +576,19 @@ A function's stack frame, after the frame is created, is laid out as
follows:
+------------------------------+
- | return PC |
- | frame pointer on entry | ← R29 points to
| ... locals ... |
| ... outgoing arguments ... |
- | unused word | ← RSP points to
+ | return PC | ← RSP points to
+ | frame pointer on entry |
+------------------------------+ ↓ lower addresses
The "return PC" is loaded to the link register, R30, as part of the
arm64 `CALL` operation.
-On entry, a function pushes R30 (the return address) and R29
-(the caller's frame pointer) onto the bottom of the stack. It then
-subtracts a constant from RSP to open its stack frame.
+On entry, a function subtracts from RSP to open its stack frame, and
+saves the values of R30 and R29 at the bottom of the frame.
+Specifically, R30 is saved at 0(RSP) and R29 is saved at -8(RSP),
+after RSP is updated.
A leaf function that does not require any stack space may omit the
saved R30 and R29.
diff --git a/src/cmd/compile/internal/arm64/ggen.go b/src/cmd/compile/internal/arm64/ggen.go
index 6ba56b992ee..14027467002 100644
--- a/src/cmd/compile/internal/arm64/ggen.go
+++ b/src/cmd/compile/internal/arm64/ggen.go
@@ -11,12 +11,10 @@ import (
)
func padframe(frame int64) int64 {
- // arm64 requires frame sizes here that are 8 mod 16.
- // With the additional (unused) slot at the bottom of the frame,
- // that makes an aligned 16 byte frame.
- // Adding a save region for LR+FP does not change the alignment.
- if frame != 0 {
- frame += (-(frame + 8)) & 15
+ // arm64 requires that the frame size (not counting saved FP&LR)
+ // be 16 bytes aligned. If not, pad it.
+ if frame%16 != 0 {
+ frame += 16 - (frame % 16)
}
return frame
}
diff --git a/src/cmd/compile/internal/arm64/ssa.go b/src/cmd/compile/internal/arm64/ssa.go
index 9f79a740c6c..7bc0e536e94 100644
--- a/src/cmd/compile/internal/arm64/ssa.go
+++ b/src/cmd/compile/internal/arm64/ssa.go
@@ -221,7 +221,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
for i := 0; i < len(args); i++ {
a := args[i]
- // Offset by size of the unused slot before start of args.
+ // Offset by size of the saved LR slot.
addr := ssagen.SpillSlotAddr(a, arm64.REGSP, base.Ctxt.Arch.FixedFrameSize)
// Look for double-register operations if we can.
if i < len(args)-1 {
diff --git a/src/cmd/compile/internal/ssagen/pgen.go b/src/cmd/compile/internal/ssagen/pgen.go
index f0776172b92..0a2010363f8 100644
--- a/src/cmd/compile/internal/ssagen/pgen.go
+++ b/src/cmd/compile/internal/ssagen/pgen.go
@@ -393,16 +393,10 @@ func StackOffset(slot ssa.LocalSlot) int32 {
case ir.PAUTO:
off = n.FrameOffset()
if base.Ctxt.Arch.FixedFrameSize == 0 {
- // x86 return address
off -= int64(types.PtrSize)
}
if buildcfg.FramePointerEnabled {
- // frame pointer
off -= int64(types.PtrSize)
- if buildcfg.GOARCH == "arm64" {
- // arm64 return address also
- off -= int64(types.PtrSize)
- }
}
}
return int32(off + slot.Off)
diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go
index 107447f04cc..1e2159579df 100644
--- a/src/cmd/compile/internal/ssagen/ssa.go
+++ b/src/cmd/compile/internal/ssagen/ssa.go
@@ -7150,7 +7150,6 @@ func defframe(s *State, e *ssafn, f *ssa.Func) {
// Insert code to zero ambiguously live variables so that the
// garbage collector only sees initialized values when it
// looks for pointers.
- // Note: lo/hi are offsets from varp and will be negative.
var lo, hi int64
// Opaque state for backend to use. Current backends use it to
@@ -7158,7 +7157,7 @@ func defframe(s *State, e *ssafn, f *ssa.Func) {
var state uint32
// Iterate through declarations. Autos are sorted in decreasing
- // frame offset order (least negative to most negative).
+ // frame offset order.
for _, n := range e.curfn.Dcl {
if !n.Needzero() {
continue
diff --git a/src/cmd/internal/obj/arm64/asm7.go b/src/cmd/internal/obj/arm64/asm7.go
index 281d705a3eb..743d09a3190 100644
--- a/src/cmd/internal/obj/arm64/asm7.go
+++ b/src/cmd/internal/obj/arm64/asm7.go
@@ -51,6 +51,7 @@ type ctxt7 struct {
blitrl *obj.Prog
elitrl *obj.Prog
autosize int32
+ extrasize int32
instoffset int64
pc int64
pool struct {
@@ -1121,7 +1122,8 @@ func span7(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
ctxt.Diag("arm64 ops not initialized, call arm64.buildop first")
}
- c := ctxt7{ctxt: ctxt, newprog: newprog, cursym: cursym, autosize: int32(p.To.Offset)}
+ c := ctxt7{ctxt: ctxt, newprog: newprog, cursym: cursym, autosize: int32(p.To.Offset & 0xffffffff), extrasize: int32(p.To.Offset >> 32)}
+ p.To.Offset &= 0xffffffff // extrasize is no longer needed
// Process literal pool and allocate initial program counter for each Prog, before
// generating branch veneers.
@@ -2117,8 +2119,8 @@ func (c *ctxt7) aclass(a *obj.Addr) int {
// a.Offset is still relative to pseudo-SP.
a.Reg = obj.REG_NONE
}
- // The frame top 16 bytes are for LR/FP
- c.instoffset = int64(c.autosize) + a.Offset - extrasize
+ // The frame top 8 or 16 bytes are for FP
+ c.instoffset = int64(c.autosize) + a.Offset - int64(c.extrasize)
return autoclass(c.instoffset)
case obj.NAME_PARAM:
@@ -2178,8 +2180,8 @@ func (c *ctxt7) aclass(a *obj.Addr) int {
// a.Offset is still relative to pseudo-SP.
a.Reg = obj.REG_NONE
}
- // The frame top 16 bytes are for LR/FP
- c.instoffset = int64(c.autosize) + a.Offset - extrasize
+ // The frame top 8 or 16 bytes are for FP
+ c.instoffset = int64(c.autosize) + a.Offset - int64(c.extrasize)
case obj.NAME_PARAM:
if a.Reg == REGSP {
diff --git a/src/cmd/internal/obj/arm64/obj7.go b/src/cmd/internal/obj/arm64/obj7.go
index a6974261451..2583e463542 100644
--- a/src/cmd/internal/obj/arm64/obj7.go
+++ b/src/cmd/internal/obj/arm64/obj7.go
@@ -36,6 +36,7 @@ import (
"cmd/internal/src"
"cmd/internal/sys"
"internal/abi"
+ "internal/buildcfg"
"log"
"math"
)
@@ -471,8 +472,6 @@ func (c *ctxt7) rewriteToUseGot(p *obj.Prog) {
obj.Nopout(p)
}
-const extrasize = 16 // space needed in the frame for LR+FP
-
func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
if cursym.Func().Text == nil || cursym.Func().Text.Link == nil {
return
@@ -522,26 +521,33 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
c.autosize = int32(textstksiz)
if p.Mark&LEAF != 0 && c.autosize == 0 {
- // A leaf function with no locals needs no frame.
+ // A leaf function with no locals has no frame.
p.From.Sym.Set(obj.AttrNoFrame, true)
}
if !p.From.Sym.NoFrame() {
// If there is a stack frame at all, it includes
- // space for the (now unused) word at [SP:SP+8].
+ // space to save the LR.
c.autosize += 8
}
- // Round up to a multiple of 16.
- c.autosize += (-c.autosize) & 15
-
if c.autosize != 0 {
- // Allocate an extra 16 bytes at the top of the frame
- // to save LR+FP.
+ extrasize := int32(0)
+ if c.autosize%16 == 8 {
+ // Allocate extra 8 bytes on the frame top to save FP
+ extrasize = 8
+ } else if c.autosize&(16-1) == 0 {
+ // Allocate extra 16 bytes to save FP for the old frame whose size is 8 mod 16
+ extrasize = 16
+ } else {
+ c.ctxt.Diag("%v: unaligned frame size %d - must be 16 aligned", p, c.autosize-8)
+ }
c.autosize += extrasize
c.cursym.Func().Locals += extrasize
- p.To.Offset = int64(c.autosize)
+ // low 32 bits for autosize
+ // high 32 bits for extrasize
+ p.To.Offset = int64(c.autosize) | int64(extrasize)<<32
} else {
// NOFRAME
p.To.Offset = 0
@@ -574,72 +580,120 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
var prologueEnd *obj.Prog
aoffset := c.autosize
- if aoffset < 16 {
- log.Fatalf("aoffset too small %d", aoffset)
+ if aoffset > 0xf0 {
+ // MOVD.W offset variant range is -0x100 to 0xf8, SP should be 16-byte aligned.
+ // so the maximum aoffset value is 0xf0.
+ aoffset = 0xf0
}
+ // Frame is non-empty. Make sure to save link register, even if
+ // it is a leaf function, so that traceback works.
q = p
+ if c.autosize > aoffset {
+ // Frame size is too large for a MOVD.W instruction. Store the frame pointer
+ // register and link register before decrementing SP, so if a signal comes
+ // during the execution of the function prologue, the traceback code will
+ // not see a half-updated stack frame.
- // Store return address and frame pointer at the top of the stack frame.
- // STP.W (R29, R30), -16(SP)
- q1 = obj.Appendp(q, c.newprog)
- q1.Pos = p.Pos
- q1.As = ASTP
- q1.From.Type = obj.TYPE_REGREG
- q1.From.Reg = REGFP
- q1.From.Offset = REGLINK
- q1.To.Type = obj.TYPE_MEM
- q1.To.Reg = REG_RSP
- q1.To.Offset = -16
- q1.Scond = C_XPRE
+ // SUB $autosize, RSP, R20
+ q1 = obj.Appendp(q, c.newprog)
+ q1.Pos = p.Pos
+ q1.As = ASUB
+ q1.From.Type = obj.TYPE_CONST
+ q1.From.Offset = int64(c.autosize)
+ q1.Reg = REGSP
+ q1.To.Type = obj.TYPE_REG
+ q1.To.Reg = REG_R20
- prologueEnd = q1
+ prologueEnd = q1
- // Update frame pointer
- q1 = obj.Appendp(q1, c.newprog)
- q1.Pos = p.Pos
- q1.As = AMOVD
- q1.From.Type = obj.TYPE_REG
- q1.From.Reg = REGSP
- q1.To.Type = obj.TYPE_REG
- q1.To.Reg = REGFP
+ // STP (R29, R30), -8(R20)
+ q1 = obj.Appendp(q1, c.newprog)
+ q1.Pos = p.Pos
+ q1.As = ASTP
+ q1.From.Type = obj.TYPE_REGREG
+ q1.From.Reg = REGFP
+ q1.From.Offset = REGLINK
+ q1.To.Type = obj.TYPE_MEM
+ q1.To.Reg = REG_R20
+ q1.To.Offset = -8
- // Allocate additional frame space.
- adj := aoffset - 16
- if adj > 0 {
- // SUB $autosize-16, RSP
- if adj < 1<<12 {
+ // This is not async preemptible, as if we open a frame
+ // at the current SP, it will clobber the saved LR.
+ q1 = c.ctxt.StartUnsafePoint(q1, c.newprog)
+
+ // MOVD R20, RSP
+ q1 = obj.Appendp(q1, c.newprog)
+ q1.Pos = p.Pos
+ q1.As = AMOVD
+ q1.From.Type = obj.TYPE_REG
+ q1.From.Reg = REG_R20
+ q1.To.Type = obj.TYPE_REG
+ q1.To.Reg = REGSP
+ q1.Spadj = c.autosize
+
+ q1 = c.ctxt.EndUnsafePoint(q1, c.newprog, -1)
+
+ if buildcfg.GOOS == "ios" {
+ // iOS does not support SA_ONSTACK. We will run the signal handler
+ // on the G stack. If we write below SP, it may be clobbered by
+ // the signal handler. So we save FP and LR after decrementing SP.
+ // STP (R29, R30), -8(RSP)
q1 = obj.Appendp(q1, c.newprog)
q1.Pos = p.Pos
- q1.As = ASUB
- q1.From.Type = obj.TYPE_CONST
- q1.From.Offset = int64(adj)
- q1.To.Type = obj.TYPE_REG
- q1.To.Reg = REGSP
- } else {
- // Constant too big for atomic subtract.
- // Materialize in tmp register first.
- q1 = obj.Appendp(q1, c.newprog)
- q1.Pos = p.Pos
- q1.As = AMOVD
- q1.From.Type = obj.TYPE_CONST
- q1.From.Offset = int64(adj)
- q1.To.Type = obj.TYPE_REG
- q1.To.Reg = REGTMP
-
- q1 = obj.Appendp(q1, c.newprog)
- q1.Pos = p.Pos
- q1.As = ASUB
- q1.From.Type = obj.TYPE_REG
- q1.From.Reg = REGTMP
- q1.To.Type = obj.TYPE_REG
+ q1.As = ASTP
+ q1.From.Type = obj.TYPE_REGREG
+ q1.From.Reg = REGFP
+ q1.From.Offset = REGLINK
+ q1.To.Type = obj.TYPE_MEM
q1.To.Reg = REGSP
+ q1.To.Offset = -8
}
- q1.Spadj = adj
+ } else {
+ // small frame, update SP and save LR in a single MOVD.W instruction.
+ // So if a signal comes during the execution of the function prologue,
+ // the traceback code will not see a half-updated stack frame.
+ // Also, on Linux, in a cgo binary we may get a SIGSETXID signal
+ // early on before the signal stack is set, as glibc doesn't allow
+ // us to block SIGSETXID. So it is important that we don't write below
+ // the SP until the signal stack is set.
+ // Luckily, all the functions from thread entry to setting the signal
+ // stack have small frames.
+ q1 = obj.Appendp(q, c.newprog)
+ q1.As = AMOVD
+ q1.Pos = p.Pos
+ q1.From.Type = obj.TYPE_REG
+ q1.From.Reg = REGLINK
+ q1.To.Type = obj.TYPE_MEM
+ q1.Scond = C_XPRE
+ q1.To.Offset = int64(-aoffset)
+ q1.To.Reg = REGSP
+ q1.Spadj = aoffset
+
+ prologueEnd = q1
+
+ // Frame pointer.
+ q1 = obj.Appendp(q1, c.newprog)
+ q1.Pos = p.Pos
+ q1.As = AMOVD
+ q1.From.Type = obj.TYPE_REG
+ q1.From.Reg = REGFP
+ q1.To.Type = obj.TYPE_MEM
+ q1.To.Reg = REGSP
+ q1.To.Offset = -8
}
prologueEnd.Pos = prologueEnd.Pos.WithXlogue(src.PosPrologueEnd)
+ q1 = obj.Appendp(q1, c.newprog)
+ q1.Pos = p.Pos
+ q1.As = ASUB
+ q1.From.Type = obj.TYPE_CONST
+ q1.From.Offset = 8
+ q1.Reg = REGSP
+ q1.To.Type = obj.TYPE_REG
+ q1.To.Reg = REGFP
+
case obj.ARET:
nocache(p)
if p.From.Type == obj.TYPE_CONST {
@@ -653,56 +707,105 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
}
p.To = obj.Addr{}
aoffset := c.autosize
- if aoffset > 0 {
- if aoffset < 16 {
- log.Fatalf("aoffset too small %d", aoffset)
- }
- adj := aoffset - 16
- if adj > 0 {
- if adj < 1<<12 {
- // ADD $adj, RSP, RSP
- p.As = AADD
- p.From.Type = obj.TYPE_CONST
- p.From.Offset = int64(adj)
- p.To.Type = obj.TYPE_REG
- p.To.Reg = REGSP
- } else {
- // Put frame size in a separate register and
- // add it in with a single instruction,
- // so we never have a partial frame during
- // the epilog. See issue 73259.
+ if c.cursym.Func().Text.Mark&LEAF != 0 {
+ if aoffset != 0 {
+ // Restore frame pointer.
+ // ADD $framesize-8, RSP, R29
+ p.As = AADD
+ p.From.Type = obj.TYPE_CONST
+ p.From.Offset = int64(c.autosize) - 8
+ p.Reg = REGSP
+ p.To.Type = obj.TYPE_REG
+ p.To.Reg = REGFP
- // MOVD $adj, REGTMP
- p.As = AMOVD
- p.From.Type = obj.TYPE_CONST
- p.From.Offset = int64(adj)
- p.To.Type = obj.TYPE_REG
- p.To.Reg = REGTMP
- // ADD REGTMP, RSP, RSP
- p = obj.Appendp(p, c.newprog)
- p.As = AADD
- p.From.Type = obj.TYPE_REG
- p.From.Reg = REGTMP
- p.To.Type = obj.TYPE_REG
- p.To.Reg = REGSP
- }
- p.Spadj = -adj
- }
-
- // Pop LR+FP.
- // LDP.P 16(RSP), (R29, R30)
- if p.As != obj.ARET {
+ // Pop stack frame.
+ // ADD $framesize, RSP, RSP
p = obj.Appendp(p, c.newprog)
+ p.As = AADD
+ p.From.Type = obj.TYPE_CONST
+ p.From.Offset = int64(c.autosize)
+ p.To.Type = obj.TYPE_REG
+ p.To.Reg = REGSP
+ p.Spadj = -c.autosize
}
- p.As = ALDP
+ } else if aoffset <= 0xF0 {
+ // small frame, restore LR and update SP in a single MOVD.P instruction.
+ // There is no correctness issue to use a single LDP for LR and FP,
+ // but the instructions are not pattern matched with the prologue's
+ // MOVD.W and MOVD, which may cause performance issue in
+ // store-forwarding.
+
+ // MOVD -8(RSP), R29
+ p.As = AMOVD
p.From.Type = obj.TYPE_MEM
p.From.Reg = REGSP
- p.From.Offset = 16
+ p.From.Offset = -8
+ p.To.Type = obj.TYPE_REG
+ p.To.Reg = REGFP
+ p = obj.Appendp(p, c.newprog)
+
+ // MOVD.P offset(RSP), R30
+ p.As = AMOVD
+ p.From.Type = obj.TYPE_MEM
p.Scond = C_XPOST
+ p.From.Offset = int64(aoffset)
+ p.From.Reg = REGSP
+ p.To.Type = obj.TYPE_REG
+ p.To.Reg = REGLINK
+ p.Spadj = -aoffset
+ } else {
+ // LDP -8(RSP), (R29, R30)
+ p.As = ALDP
+ p.From.Type = obj.TYPE_MEM
+ p.From.Offset = -8
+ p.From.Reg = REGSP
p.To.Type = obj.TYPE_REGREG
p.To.Reg = REGFP
p.To.Offset = REGLINK
- p.Spadj = -16
+
+ if aoffset < 1<<12 {
+ // ADD $aoffset, RSP, RSP
+ q = newprog()
+ q.As = AADD
+ q.From.Type = obj.TYPE_CONST
+ q.From.Offset = int64(aoffset)
+ q.To.Type = obj.TYPE_REG
+ q.To.Reg = REGSP
+ q.Spadj = -aoffset
+ q.Pos = p.Pos
+ q.Link = p.Link
+ p.Link = q
+ p = q
+ } else {
+ // Put frame size in a separate register and
+ // add it in with a single instruction,
+ // so we never have a partial frame during
+ // the epilog. See issue 73259.
+
+ // MOVD $aoffset, REGTMP
+ q = newprog()
+ q.As = AMOVD
+ q.From.Type = obj.TYPE_CONST
+ q.From.Offset = int64(aoffset)
+ q.To.Type = obj.TYPE_REG
+ q.To.Reg = REGTMP
+ q.Pos = p.Pos
+ q.Link = p.Link
+ p.Link = q
+ p = q
+ // ADD REGTMP, RSP, RSP
+ q = newprog()
+ q.As = AADD
+ q.From.Type = obj.TYPE_REG
+ q.From.Reg = REGTMP
+ q.To.Type = obj.TYPE_REG
+ q.To.Reg = REGSP
+ q.Spadj = -aoffset
+ q.Pos = p.Pos
+ q.Link = p.Link
+ p.Link = q
+ p = q
+ }
}
// If enabled, this code emits 'MOV PC, R27' before every 'MOV LR, PC',
@@ -765,11 +868,10 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
p.From.Type = obj.TYPE_REG
p.From.Reg = REGLINK
} else {
- /* MOVD framesize-8(RSP), Rd */
+ /* MOVD (RSP), Rd */
p.As = AMOVD
p.From.Type = obj.TYPE_MEM
p.From.Reg = REGSP
- p.From.Offset = int64(c.autosize - 8)
}
}
if p.To.Type == obj.TYPE_REG && p.To.Reg == REGSP && p.Spadj == 0 {
@@ -804,12 +906,6 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
p.From.Reg = int16(REG_LSL + r + (shift&7)<<5)
p.From.Offset = 0
}
- if p.To.Type == obj.TYPE_MEM && p.To.Reg == REG_RSP && (p.Scond == C_XPRE || p.Scond == C_XPOST) {
- p.Spadj += int32(-p.To.Offset)
- }
- if p.From.Type == obj.TYPE_MEM && p.From.Reg == REG_RSP && (p.Scond == C_XPRE || p.Scond == C_XPOST) {
- p.Spadj += int32(-p.From.Offset)
- }
}
}
diff --git a/src/cmd/link/internal/amd64/obj.go b/src/cmd/link/internal/amd64/obj.go
index 761496549f9..3a6141b9091 100644
--- a/src/cmd/link/internal/amd64/obj.go
+++ b/src/cmd/link/internal/amd64/obj.go
@@ -51,16 +51,15 @@ func Init() (*sys.Arch, ld.Arch) {
Plan9Magic: uint32(4*26*26 + 7),
Plan9_64Bit: true,
- Adddynrel: adddynrel,
- Archinit: archinit,
- Archreloc: archreloc,
- Archrelocvariant: archrelocvariant,
- Gentext: gentext,
- Machoreloc1: machoreloc1,
- MachorelocSize: 8,
- PEreloc1: pereloc1,
- TLSIEtoLE: tlsIEtoLE,
- ReturnAddressAtTopOfFrame: true,
+ Adddynrel: adddynrel,
+ Archinit: archinit,
+ Archreloc: archreloc,
+ Archrelocvariant: archrelocvariant,
+ Gentext: gentext,
+ Machoreloc1: machoreloc1,
+ MachorelocSize: 8,
+ PEreloc1: pereloc1,
+ TLSIEtoLE: tlsIEtoLE,
ELF: ld.ELFArch{
Linuxdynld: "/lib64/ld-linux-x86-64.so.2",
diff --git a/src/cmd/link/internal/arm64/obj.go b/src/cmd/link/internal/arm64/obj.go
index e1e4ade8183..3d358155bad 100644
--- a/src/cmd/link/internal/arm64/obj.go
+++ b/src/cmd/link/internal/arm64/obj.go
@@ -47,18 +47,17 @@ func Init() (*sys.Arch, ld.Arch) {
Dwarfreglr: dwarfRegLR,
TrampLimit: 0x7c00000, // 26-bit signed offset * 4, leave room for PLT etc.
- Adddynrel: adddynrel,
- Archinit: archinit,
- Archreloc: archreloc,
- Archrelocvariant: archrelocvariant,
- Extreloc: extreloc,
- Gentext: gentext,
- GenSymsLate: gensymlate,
- Machoreloc1: machoreloc1,
- MachorelocSize: 8,
- PEreloc1: pereloc1,
- Trampoline: trampoline,
- ReturnAddressAtTopOfFrame: true,
+ Adddynrel: adddynrel,
+ Archinit: archinit,
+ Archreloc: archreloc,
+ Archrelocvariant: archrelocvariant,
+ Extreloc: extreloc,
+ Gentext: gentext,
+ GenSymsLate: gensymlate,
+ Machoreloc1: machoreloc1,
+ MachorelocSize: 8,
+ PEreloc1: pereloc1,
+ Trampoline: trampoline,
ELF: ld.ELFArch{
Androiddynld: "/system/bin/linker64",
diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go
index c4d12a5488d..0003938ef2e 100644
--- a/src/cmd/link/internal/ld/dwarf.go
+++ b/src/cmd/link/internal/ld/dwarf.go
@@ -1544,14 +1544,9 @@ func (d *dwctxt) writeframes(fs loader.Sym) dwarfSecInfo {
if pcsp.Value > 0 {
// The return address is preserved at (CFA-frame_size)
// after a stack frame has been allocated.
- off := -spdelta
- if thearch.ReturnAddressAtTopOfFrame {
- // Except arm64, which has it at the top of frame.
- off = -int64(d.arch.PtrSize)
- }
deltaBuf = append(deltaBuf, dwarf.DW_CFA_offset_extended_sf)
deltaBuf = dwarf.AppendUleb128(deltaBuf, uint64(thearch.Dwarfreglr))
- deltaBuf = dwarf.AppendSleb128(deltaBuf, off/dataAlignmentFactor)
+ deltaBuf = dwarf.AppendSleb128(deltaBuf, -spdelta/dataAlignmentFactor)
} else {
// The return address is restored into the link register
// when a stack frame has been de-allocated.
diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
index 5f5ebfc1d98..2c861129b52 100644
--- a/src/cmd/link/internal/ld/lib.go
+++ b/src/cmd/link/internal/ld/lib.go
@@ -263,10 +263,6 @@ type Arch struct {
// optional override for assignAddress
AssignAddress func(ldr *loader.Loader, sect *sym.Section, n int, s loader.Sym, va uint64, isTramp bool) (*sym.Section, int, uint64)
- // Reports whether the return address is stored at the top (highest address)
- // of the stack frame.
- ReturnAddressAtTopOfFrame bool
-
// ELF specific information.
ELF ELFArch
}
diff --git a/src/cmd/link/internal/ld/stackcheck.go b/src/cmd/link/internal/ld/stackcheck.go
index 14cd3a22384..98e7edaeb14 100644
--- a/src/cmd/link/internal/ld/stackcheck.go
+++ b/src/cmd/link/internal/ld/stackcheck.go
@@ -9,6 +9,7 @@ import (
"cmd/internal/objabi"
"cmd/link/internal/loader"
"fmt"
+ "internal/buildcfg"
"sort"
"strings"
)
@@ -61,6 +62,10 @@ func (ctxt *Link) doStackCheck() {
// that there are at least StackLimit bytes available below SP
// when morestack returns.
limit := objabi.StackNosplit(*flagRace) - sc.callSize
+ if buildcfg.GOARCH == "arm64" {
+ // Need an extra 8 bytes below SP to save FP.
+ limit -= 8
+ }
// Compute stack heights without any back-tracking information.
// This will almost certainly succeed and we can simply
diff --git a/src/cmd/link/internal/x86/obj.go b/src/cmd/link/internal/x86/obj.go
index a4885fde8fd..4336f01ea3d 100644
--- a/src/cmd/link/internal/x86/obj.go
+++ b/src/cmd/link/internal/x86/obj.go
@@ -50,14 +50,13 @@ func Init() (*sys.Arch, ld.Arch) {
Plan9Magic: uint32(4*11*11 + 7),
- Adddynrel: adddynrel,
- Archinit: archinit,
- Archreloc: archreloc,
- Archrelocvariant: archrelocvariant,
- Gentext: gentext,
- Machoreloc1: machoreloc1,
- PEreloc1: pereloc1,
- ReturnAddressAtTopOfFrame: true,
+ Adddynrel: adddynrel,
+ Archinit: archinit,
+ Archreloc: archreloc,
+ Archrelocvariant: archrelocvariant,
+ Gentext: gentext,
+ Machoreloc1: machoreloc1,
+ PEreloc1: pereloc1,
ELF: ld.ELFArch{
Linuxdynld: "/lib/ld-linux.so.2",
diff --git a/src/runtime/asm_arm64.s b/src/runtime/asm_arm64.s
index aa49a27a75d..a0e82ec830f 100644
--- a/src/runtime/asm_arm64.s
+++ b/src/runtime/asm_arm64.s
@@ -50,7 +50,9 @@ TEXT _rt0_arm64_lib(SB),NOSPLIT,$184
CBZ R4, nocgo
MOVD $_rt0_arm64_lib_go(SB), R0
MOVD $0, R1
+ SUB $16, RSP // reserve 16 bytes for sp-8 where fp may be saved.
BL (R4)
+ ADD $16, RSP
B restore
nocgo:
@@ -369,6 +371,7 @@ switch:
BL runtime·save_g(SB)
MOVD (g_sched+gobuf_sp)(g), R0
MOVD R0, RSP
+ MOVD (g_sched+gobuf_bp)(g), R29
MOVD $0, (g_sched+gobuf_sp)(g)
MOVD $0, (g_sched+gobuf_bp)(g)
RET
@@ -378,8 +381,8 @@ noswitch:
// Using a tail call here cleans up tracebacks since we won't stop
// at an intermediate systemstack.
MOVD 0(R26), R3 // code pointer
- ADD $16, RSP
- LDP.P 16(RSP), (R29,R30) // restore FP, LR
+ MOVD.P 16(RSP), R30 // restore LR
+ SUB $8, RSP, R29 // restore FP
B (R3)
// func switchToCrashStack0(fn func())
@@ -1048,7 +1051,7 @@ again:
// Smashes R0.
TEXT gosave_systemstack_switch<>(SB),NOSPLIT|NOFRAME,$0
MOVD $runtime·systemstack_switch(SB), R0
- ADD $12, R0 // get past prologue
+ ADD $8, R0 // get past prologue
MOVD R0, (g_sched+gobuf_pc)(g)
MOVD RSP, R0
MOVD R0, (g_sched+gobuf_sp)(g)
@@ -1066,7 +1069,9 @@ TEXT gosave_systemstack_switch<>(SB),NOSPLIT|NOFRAME,$0
TEXT ·asmcgocall_no_g(SB),NOSPLIT,$0-16
MOVD fn+0(FP), R1
MOVD arg+8(FP), R0
+ SUB $16, RSP // skip over saved frame pointer below RSP
BL (R1)
+ ADD $16, RSP // skip over saved frame pointer below RSP
RET
// func asmcgocall(fn, arg unsafe.Pointer) int32
@@ -1231,9 +1236,9 @@ havem:
BL runtime·save_g(SB)
MOVD (g_sched+gobuf_sp)(g), R4 // prepare stack as R4
MOVD (g_sched+gobuf_pc)(g), R5
- MOVD R5, -8(R4)
+ MOVD R5, -48(R4)
MOVD (g_sched+gobuf_bp)(g), R5
- MOVD R5, -16(R4)
+ MOVD R5, -56(R4)
// Gather our arguments into registers.
MOVD fn+0(FP), R1
MOVD frame+8(FP), R2
@@ -1247,7 +1252,7 @@ havem:
CALL (R0) // indirect call to bypass nosplit check. We're on a different stack now.
// Restore g->sched (== m->curg->sched) from saved values.
- MOVD 40(RSP), R5
+ MOVD 0(RSP), R5
MOVD R5, (g_sched+gobuf_pc)(g)
MOVD RSP, R4
ADD $48, R4, R4
@@ -1485,57 +1490,10 @@ GLOBL debugCallFrameTooLarge<>(SB), RODATA, $20 // Size duplicated below
//
// This is ABIInternal because Go code injects its PC directly into new
// goroutine stacks.
-//
-// State before debugger starts doing anything:
-// | current |
-// | stack |
-// +-------------+ <- SP = origSP
-// stopped executing at PC = origPC
-// some values are in LR (origLR) and FP (origFP)
-//
-// After debugger has done steps 1-6 above:
-// | current |
-// | stack |
-// +-------------+ <- origSP
-// | ----- | (used to be a slot to store frame pointer on entry to origPC's frame.)
-// +-------------+
-// | origLR |
-// +-------------+ <- SP
-// | ----- |
-// +-------------+
-// | argsize |
-// +-------------+
-// LR = origPC, PC = debugCallV2
-//
-// debugCallV2 then modifies the stack up to the "good" label:
-// | current |
-// | stack |
-// +-------------+ <- origSP
-// | ----- | (used to be a slot to store frame pointer on entry to origPC's frame.)
-// +-------------+
-// | origLR |
-// +-------------+ <- where debugger left SP
-// | origPC |
-// +-------------+
-// | origFP |
-// +-------------+ <- FP = SP + 256
-// | saved |
-// | registers |
-// | (224 bytes) |
-// +-------------+ <- SP + 32
-// | space for |
-// | outargs |
-// +-------------+ <- SP + 8
-// | argsize |
-// +-------------+ <- SP
-
TEXT runtime·debugCallV2(SB),NOSPLIT|NOFRAME,$0-0
- MOVD R30, -8(RSP) // save origPC
- MOVD -16(RSP), R30 // save argsize in R30 temporarily
- MOVD.W R29, -16(RSP) // push origFP
- MOVD RSP, R29 // frame pointer chain now set up
- SUB $256, RSP, RSP // allocate frame
- MOVD R30, (RSP) // Save argsize on the stack
+ STP (R29, R30), -280(RSP)
+ SUB $272, RSP, RSP
+ SUB $8, RSP, R29
// Save all registers that may contain pointers so they can be
// conservatively scanned.
//
@@ -1557,8 +1515,7 @@ TEXT runtime·debugCallV2(SB),NOSPLIT|NOFRAME,$0-0
STP (R0, R1), (4*8)(RSP)
// Perform a safe-point check.
- MOVD 264(RSP), R0 // origPC
- MOVD R0, 8(RSP)
+ MOVD R30, 8(RSP) // Caller's PC
CALL runtime·debugCallCheck(SB)
MOVD 16(RSP), R0
CBZ R0, good
@@ -1602,7 +1559,7 @@ good:
CALL runtime·debugCallWrap(SB); \
JMP restore
- MOVD (RSP), R0 // the argument frame size
+ MOVD 256(RSP), R0 // the argument frame size
DEBUG_CALL_DISPATCH(debugCall32<>, 32)
DEBUG_CALL_DISPATCH(debugCall64<>, 64)
DEBUG_CALL_DISPATCH(debugCall128<>, 128)
@@ -1650,9 +1607,9 @@ restore:
LDP (6*8)(RSP), (R2, R3)
LDP (4*8)(RSP), (R0, R1)
- MOVD 272(RSP), R30 // restore old lr (saved by (*sigctxt).pushCall)
- LDP 256(RSP), (R29, R27) // restore old fp, set up resumption address
- ADD $288, RSP, RSP // Pop frame, LR+FP, and block pushed by (*sigctxt).pushCall
+ LDP -8(RSP), (R29, R27)
+ ADD $288, RSP, RSP // Add 16 more bytes, see saveSigContext
+ MOVD -16(RSP), R30 // restore old lr
JMP (R27)
// runtime.debugCallCheck assumes that functions defined with the
diff --git a/src/runtime/mkpreempt.go b/src/runtime/mkpreempt.go
index 9064cae039f..769c4ffc5c9 100644
--- a/src/runtime/mkpreempt.go
+++ b/src/runtime/mkpreempt.go
@@ -488,18 +488,26 @@ func genARM64(g *gen) {
l.stack += 8 // SP needs 16-byte alignment
}
- // allocate frame, save PC (in R30), FP (in R29) of interrupted instruction
- p("STP.W (R29, R30), -16(RSP)")
- p("MOVD RSP, R29") // set up new frame pointer
+ // allocate frame, save PC of interrupted instruction (in LR)
+ p("MOVD R30, %d(RSP)", -l.stack)
p("SUB $%d, RSP", l.stack)
+ p("MOVD R29, -8(RSP)") // save frame pointer (only used on Linux)
+ p("SUB $8, RSP, R29") // set up new frame pointer
+ // On iOS, save the LR again after decrementing SP. We run the
+ // signal handler on the G stack (as it doesn't support sigaltstack),
+ // so any writes below SP may be clobbered.
+ p("#ifdef GOOS_ios")
+ p("MOVD R30, (RSP)")
+ p("#endif")
l.save(g)
p("CALL ·asyncPreempt2(SB)")
l.restore(g)
- p("MOVD %d(RSP), R30", l.stack+16) // sigctxt.pushCall has pushed LR (at interrupt) on stack, restore it
- p("LDP %d(RSP), (R29, R27)", l.stack) // Restore frame pointer. Load PC into regtmp.
- p("ADD $%d, RSP", l.stack+32) // pop frame (including the space pushed by sigctxt.pushCall)
+ p("MOVD %d(RSP), R30", l.stack) // sigctxt.pushCall has pushed LR (at interrupt) on stack, restore it
+ p("MOVD -8(RSP), R29") // restore frame pointer
+ p("MOVD (RSP), R27") // load PC to REGTMP
+ p("ADD $%d, RSP", l.stack+16) // pop frame (including the space pushed by sigctxt.pushCall)
p("RET (R27)")
}
diff --git a/src/runtime/panic.go b/src/runtime/panic.go
index 04b3afe1682..8c91c9435ab 100644
--- a/src/runtime/panic.go
+++ b/src/runtime/panic.go
@@ -1379,10 +1379,10 @@ func recovery(gp *g) {
// the caller
gp.sched.bp = fp - 2*goarch.PtrSize
case goarch.IsArm64 != 0:
- // on arm64, the first two words of the frame are caller's PC
- // (the saved LR register) and the caller's BP.
- // Coincidentally, the same as amd64.
- gp.sched.bp = fp - 2*goarch.PtrSize
+ // on arm64, the architectural bp points one word higher
+ // than the sp. fp is totally useless to us here, because it
+ // only gets us to the caller's fp.
+ gp.sched.bp = sp - goarch.PtrSize
}
gogo(&gp.sched)
}
diff --git a/src/runtime/preempt_arm64.s b/src/runtime/preempt_arm64.s
index f4248cac257..31ec9d940f7 100644
--- a/src/runtime/preempt_arm64.s
+++ b/src/runtime/preempt_arm64.s
@@ -4,9 +4,13 @@
#include "textflag.h"
TEXT ·asyncPreempt(SB),NOSPLIT|NOFRAME,$0-0
- STP.W (R29, R30), -16(RSP)
- MOVD RSP, R29
+ MOVD R30, -496(RSP)
SUB $496, RSP
+ MOVD R29, -8(RSP)
+ SUB $8, RSP, R29
+ #ifdef GOOS_ios
+ MOVD R30, (RSP)
+ #endif
STP (R0, R1), 8(RSP)
STP (R2, R3), 24(RSP)
STP (R4, R5), 40(RSP)
@@ -74,7 +78,8 @@ TEXT ·asyncPreempt(SB),NOSPLIT|NOFRAME,$0-0
LDP 40(RSP), (R4, R5)
LDP 24(RSP), (R2, R3)
LDP 8(RSP), (R0, R1)
- MOVD 512(RSP), R30
- LDP 496(RSP), (R29, R27)
- ADD $528, RSP
+ MOVD 496(RSP), R30
+ MOVD -8(RSP), R29
+ MOVD (RSP), R27
+ ADD $512, RSP
RET (R27)
diff --git a/src/runtime/race_arm64.s b/src/runtime/race_arm64.s
index feaa328d4c0..5df650105bb 100644
--- a/src/runtime/race_arm64.s
+++ b/src/runtime/race_arm64.s
@@ -397,7 +397,7 @@ TEXT racecallatomic<>(SB), NOSPLIT, $0
// R3 = addr of incoming arg list
// Trigger SIGSEGV early.
- MOVD 72(RSP), R3 // 1st arg is addr. after two small frames (32 bytes each), get it at 72(RSP)
+ MOVD 40(RSP), R3 // 1st arg is addr. after two times BL, get it at 40(RSP)
MOVB (R3), R13 // segv here if addr is bad
// Check that addr is within [arenastart, arenaend) or within [racedatastart, racedataend).
MOVD runtime·racearenastart(SB), R10
@@ -417,11 +417,10 @@ racecallatomic_ok:
// Addr is within the good range, call the atomic function.
load_g
MOVD g_racectx(g), R0 // goroutine context
- MOVD 56(RSP), R1 // caller pc
+ MOVD 16(RSP), R1 // caller pc
MOVD R9, R2 // pc
- ADD $72, RSP, R3
- BL racecall<>(SB)
- RET
+ ADD $40, RSP, R3
+ JMP racecall<>(SB) // does not return
racecallatomic_ignore:
// Addr is outside the good range.
// Call __tsan_go_ignore_sync_begin to ignore synchronization during the atomic op.
@@ -436,9 +435,9 @@ racecallatomic_ignore:
// racecall will call LLVM race code which might clobber R28 (g)
load_g
MOVD g_racectx(g), R0 // goroutine context
- MOVD 56(RSP), R1 // caller pc
+ MOVD 16(RSP), R1 // caller pc
MOVD R9, R2 // pc
- ADD $72, RSP, R3 // arguments
+ ADD $40, RSP, R3 // arguments
BL racecall<>(SB)
// Call __tsan_go_ignore_sync_end.
MOVD $__tsan_go_ignore_sync_end(SB), R9
@@ -477,6 +476,10 @@ TEXT racecall<>(SB), NOSPLIT|NOFRAME, $0-0
MOVD (g_sched+gobuf_sp)(R11), R12
MOVD R12, RSP
call:
+ // Decrement SP past where the frame pointer is saved in the Go arm64
+ // ABI (one word below the stack pointer) so the race detector library
+ // code doesn't clobber it
+ SUB $16, RSP
BL R9
MOVD R19, RSP
JMP (R20)
diff --git a/src/runtime/signal_arm64.go b/src/runtime/signal_arm64.go
index 61dad507219..af7d29f9de1 100644
--- a/src/runtime/signal_arm64.go
+++ b/src/runtime/signal_arm64.go
@@ -8,6 +8,7 @@ package runtime
import (
"internal/abi"
+ "internal/goarch"
"internal/runtime/sys"
"unsafe"
)
@@ -62,11 +63,18 @@ func (c *sigctxt) preparePanic(sig uint32, gp *g) {
// We arrange lr, and pc to pretend the panicking
// function calls sigpanic directly.
// Always save LR to stack so that panics in leaf
- // functions are correctly handled.
- // This extra space is known to gentraceback.
+ // functions are correctly handled. This smashes
+ // the stack frame but we're not going back there
+ // anyway.
sp := c.sp() - sys.StackAlign // needs only sizeof uint64, but must align the stack
c.set_sp(sp)
*(*uint64)(unsafe.Pointer(uintptr(sp))) = c.lr()
+ // Make sure a valid frame pointer is saved on the stack so that the
+ // frame pointer checks in adjustframe are happy, if they're enabled.
+ // Frame pointer unwinding won't visit the sigpanic frame, since
+ // sigpanic will save the same frame pointer before calling into a panic
+ // function.
+ *(*uint64)(unsafe.Pointer(uintptr(sp - goarch.PtrSize))) = c.r29()
pc := gp.sigpc
@@ -88,6 +96,10 @@ func (c *sigctxt) pushCall(targetPC, resumePC uintptr) {
sp := c.sp() - 16 // SP needs 16-byte alignment
c.set_sp(sp)
*(*uint64)(unsafe.Pointer(uintptr(sp))) = c.lr()
+ // Make sure a valid frame pointer is saved on the stack so that the
+ // frame pointer checks in adjustframe are happy, if they're enabled.
+ // This is not actually used for unwinding.
+ *(*uint64)(unsafe.Pointer(uintptr(sp - goarch.PtrSize))) = c.r29()
// Set up PC and LR to pretend the function being signaled
// calls targetPC at resumePC.
c.set_lr(uint64(resumePC))
diff --git a/src/runtime/stack.go b/src/runtime/stack.go
index 5eaceec6da1..55e97e77afa 100644
--- a/src/runtime/stack.go
+++ b/src/runtime/stack.go
@@ -579,27 +579,23 @@ var ptrnames = []string{
// | args to callee |
// +------------------+ <- frame->sp
//
-// (arm64)
+// (arm)
// +------------------+
// | args from caller |
// +------------------+ <- frame->argp
-// | |
-// +------------------+ <- frame->fp (aka caller's sp)
-// | return address |
+// | caller's retaddr |
// +------------------+
-// | caller's FP | (frame pointer always enabled: TODO)
+// | caller's FP (*) | (*) on ARM64, if framepointer_enabled && varp > sp
// +------------------+ <- frame->varp
// | locals |
// +------------------+
// | args to callee |
// +------------------+
-// | |
+// | return address |
// +------------------+ <- frame->sp
//
// varp > sp means that the function has a frame;
// varp == sp means frameless function.
-//
-// Alignment padding, if needed, will be between "locals" and "args to callee".
type adjustinfo struct {
old stack
@@ -713,8 +709,7 @@ func adjustframe(frame *stkframe, adjinfo *adjustinfo) {
}
// Adjust saved frame pointer if there is one.
- if goarch.ArchFamily == goarch.AMD64 && frame.argp-frame.varp == 2*goarch.PtrSize ||
- goarch.ArchFamily == goarch.ARM64 && frame.argp-frame.varp == 3*goarch.PtrSize {
+ if (goarch.ArchFamily == goarch.AMD64 || goarch.ArchFamily == goarch.ARM64) && frame.argp-frame.varp == 2*goarch.PtrSize {
if stackDebug >= 3 {
print(" saved bp\n")
}
@@ -728,7 +723,10 @@ func adjustframe(frame *stkframe, adjinfo *adjustinfo) {
throw("bad frame pointer")
}
}
- // This is the caller's frame pointer saved in the current frame.
+ // On AMD64, this is the caller's frame pointer saved in the current
+ // frame.
+ // On ARM64, this is the frame pointer of the caller's caller saved
+ // by the caller in its frame (one word below its SP).
adjustpointer(adjinfo, unsafe.Pointer(frame.varp))
}
diff --git a/src/runtime/testdata/testprog/badtraceback.go b/src/runtime/testdata/testprog/badtraceback.go
index 36575f765db..455118a5437 100644
--- a/src/runtime/testdata/testprog/badtraceback.go
+++ b/src/runtime/testdata/testprog/badtraceback.go
@@ -41,11 +41,6 @@ func badLR2(arg int) {
if runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" {
lrOff = 32 // FIXED_FRAME or sys.MinFrameSize
}
- if runtime.GOARCH == "arm64" {
- // skip 8 bytes at bottom of parent frame, then point
- // to the 8 bytes of the saved PC at the top of the frame.
- lrOff = 16
- }
lrPtr := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&arg)) - lrOff))
*lrPtr = 0xbad
diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go
index 1c3e679a02b..8882c306edb 100644
--- a/src/runtime/traceback.go
+++ b/src/runtime/traceback.go
@@ -175,11 +175,6 @@ func (u *unwinder) initAt(pc0, sp0, lr0 uintptr, gp *g, flags unwindFlags) {
// Start in the caller's frame.
if frame.pc == 0 {
if usesLR {
- // TODO: this isn't right on arm64. But also, this should
- // ~never happen. Calling a nil function will panic
- // when loading the PC out of the closure, not when
- // branching to that PC. (Closures should always have
- // valid PCs in their first word.)
frame.pc = *(*uintptr)(unsafe.Pointer(frame.sp))
frame.lr = 0
} else {
@@ -374,11 +369,7 @@ func (u *unwinder) resolveInternal(innermost, isSyscall bool) {
var lrPtr uintptr
if usesLR {
if innermost && frame.sp < frame.fp || frame.lr == 0 {
- if GOARCH == "arm64" {
- lrPtr = frame.fp - goarch.PtrSize
- } else {
- lrPtr = frame.sp
- }
+ lrPtr = frame.sp
frame.lr = *(*uintptr)(unsafe.Pointer(lrPtr))
}
} else {
@@ -394,17 +385,24 @@ func (u *unwinder) resolveInternal(innermost, isSyscall bool) {
// On x86, call instruction pushes return PC before entering new function.
frame.varp -= goarch.PtrSize
}
- if GOARCH == "arm64" && frame.varp > frame.sp {
- frame.varp -= goarch.PtrSize // LR have been saved, skip over it.
- }
// For architectures with frame pointers, if there's
// a frame, then there's a saved frame pointer here.
//
// NOTE: This code is not as general as it looks.
- // On x86 and arm64, the ABI is to save the frame pointer word at the
+ // On x86, the ABI is to save the frame pointer word at the
// top of the stack frame, so we have to back down over it.
- // No other architectures are framepointer-enabled at the moment.
+ // On arm64, the frame pointer should be at the bottom of
+ // the stack (with R29 (aka FP) = RSP), in which case we would
+ // not want to do the subtraction here. But we started out without
+ // any frame pointer, and when we wanted to add it, we didn't
+ // want to break all the assembly doing direct writes to 8(RSP)
+ // to set the first parameter to a called function.
+ // So we decided to write the FP link *below* the stack pointer
+ // (with R29 = RSP - 8 in Go functions).
+ // This is technically ABI-compatible but not standard.
+ // And it happens to end up mimicking the x86 layout.
+ // Other architectures may make different decisions.
if frame.varp > frame.sp && framepointer_enabled {
frame.varp -= goarch.PtrSize
}
@@ -564,7 +562,7 @@ func (u *unwinder) finishInternal() {
gp := u.g.ptr()
if u.flags&(unwindPrintErrors|unwindSilentErrors) == 0 && u.frame.sp != gp.stktopsp {
print("runtime: g", gp.goid, ": frame.sp=", hex(u.frame.sp), " top=", hex(gp.stktopsp), "\n")
- print("\tstack=[", hex(gp.stack.lo), "-", hex(gp.stack.hi), "]\n")
+ print("\tstack=[", hex(gp.stack.lo), "-", hex(gp.stack.hi), "\n")
throw("traceback did not unwind completely")
}
}
diff --git a/test/nosplit.go b/test/nosplit.go
index 1f943fa18c3..4b4c93b1d06 100644
--- a/test/nosplit.go
+++ b/test/nosplit.go
@@ -142,7 +142,7 @@ start 136
# (CallSize is 32 on ppc64, 8 on amd64 for frame pointer.)
start 96 nosplit
start 100 nosplit; REJECT ppc64 ppc64le
-start 104 nosplit; REJECT ppc64 ppc64le
+start 104 nosplit; REJECT ppc64 ppc64le arm64
start 108 nosplit; REJECT ppc64 ppc64le
start 112 nosplit; REJECT ppc64 ppc64le arm64
start 116 nosplit; REJECT ppc64 ppc64le
@@ -160,7 +160,7 @@ start 136 nosplit; REJECT
# Because AMD64 uses frame pointer, it has 8 fewer bytes.
start 96 nosplit call f; f 0 nosplit
start 100 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
-start 104 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
+start 104 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le arm64
start 108 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
start 112 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 arm64
start 116 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
@@ -176,7 +176,7 @@ start 136 nosplit call f; f 0 nosplit; REJECT
# Architectures differ in the same way as before.
start 96 nosplit call f; f 0 call f
start 100 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
-start 104 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
+start 104 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64
start 108 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
start 112 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64
start 116 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
@@ -189,7 +189,7 @@ start 136 nosplit call f; f 0 call f; REJECT
# Indirect calls are assumed to be splitting functions.
start 96 nosplit callind
start 100 nosplit callind; REJECT ppc64 ppc64le
-start 104 nosplit callind; REJECT ppc64 ppc64le amd64
+start 104 nosplit callind; REJECT ppc64 ppc64le amd64 arm64
start 108 nosplit callind; REJECT ppc64 ppc64le amd64
start 112 nosplit callind; REJECT ppc64 ppc64le amd64 arm64
start 116 nosplit callind; REJECT ppc64 ppc64le amd64
From f86ddb54b5b8e4cb30b8fe2f9f3a2c0c172e7c37 Mon Sep 17 00:00:00 2001
From: Ian Alexander
Date: Wed, 20 Aug 2025 19:21:56 -0400
Subject: [PATCH 033/421] cmd/go: refactor usage of `ForceUseModules`
This commit refactors usage of the global variable `ForceUseModules`
to the global LoaderState field of the same name.
This commit is part of the overall effort to eliminate global
modloader state.
[git-generate]
cd src/cmd/go/internal/modload
rf 'mv State.forceUseModules State.ForceUseModules'
rf 'ex { ForceUseModules -> LoaderState.ForceUseModules }'
for dir in load modcmd modget run toolchain work workcmd ; do
cd ../${dir}
rf 'ex {
import "cmd/go/internal/modload";
modload.ForceUseModules -> modload.LoaderState.ForceUseModules
}'
done
cd ../modload
rf 'add State.initialized \
// ForceUseModules may be set to force modules to be enabled when\
// GO111MODULE=auto or to report an error when GO111MODULE=off.'
rf 'rm ForceUseModules'
Change-Id: Ibdecfd273ff672516c9eb86279e5dfc6cdecb2ea
Reviewed-on: https://go-review.googlesource.com/c/go/+/698057
Reviewed-by: Michael Matloob
Reviewed-by: Michael Matloob
LUCI-TryBot-Result: Go LUCI
---
src/cmd/go/internal/load/pkg.go | 2 +-
src/cmd/go/internal/modcmd/download.go | 2 +-
src/cmd/go/internal/modcmd/graph.go | 2 +-
src/cmd/go/internal/modcmd/init.go | 2 +-
src/cmd/go/internal/modcmd/tidy.go | 2 +-
src/cmd/go/internal/modcmd/vendor.go | 2 +-
src/cmd/go/internal/modcmd/verify.go | 2 +-
src/cmd/go/internal/modcmd/why.go | 2 +-
src/cmd/go/internal/modget/get.go | 2 +-
src/cmd/go/internal/modload/init.go | 21 ++++++++++-----------
src/cmd/go/internal/run/run.go | 2 +-
src/cmd/go/internal/toolchain/select.go | 4 ++--
src/cmd/go/internal/work/build.go | 2 +-
src/cmd/go/internal/workcmd/init.go | 2 +-
src/cmd/go/internal/workcmd/sync.go | 2 +-
src/cmd/go/internal/workcmd/use.go | 2 +-
16 files changed, 26 insertions(+), 27 deletions(-)
diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go
index 1f791546f90..48b2e70d74b 100644
--- a/src/cmd/go/internal/load/pkg.go
+++ b/src/cmd/go/internal/load/pkg.go
@@ -3348,7 +3348,7 @@ func GoFilesPackage(ctx context.Context, opts PackageOpts, gofiles []string) *Pa
// would cause it to be interpreted differently if it were the main module
// (replace, exclude).
func PackagesAndErrorsOutsideModule(ctx context.Context, opts PackageOpts, args []string) ([]*Package, error) {
- if !modload.ForceUseModules {
+ if !modload.LoaderState.ForceUseModules {
panic("modload.ForceUseModules must be true")
}
if modload.RootMode != modload.NoRoot {
diff --git a/src/cmd/go/internal/modcmd/download.go b/src/cmd/go/internal/modcmd/download.go
index 2f4feae8f25..6d12d689f0f 100644
--- a/src/cmd/go/internal/modcmd/download.go
+++ b/src/cmd/go/internal/modcmd/download.go
@@ -112,7 +112,7 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
// Check whether modules are enabled and whether we're in a module.
- modload.ForceUseModules = true
+ modload.LoaderState.ForceUseModules = true
modload.ExplicitWriteGoMod = true
haveExplicitArgs := len(args) > 0
diff --git a/src/cmd/go/internal/modcmd/graph.go b/src/cmd/go/internal/modcmd/graph.go
index 172c1dda5ce..4abae33129a 100644
--- a/src/cmd/go/internal/modcmd/graph.go
+++ b/src/cmd/go/internal/modcmd/graph.go
@@ -57,7 +57,7 @@ func runGraph(ctx context.Context, cmd *base.Command, args []string) {
if len(args) > 0 {
base.Fatalf("go: 'go mod graph' accepts no arguments")
}
- modload.ForceUseModules = true
+ modload.LoaderState.ForceUseModules = true
modload.RootMode = modload.NeedRoot
goVersion := graphGo.String()
diff --git a/src/cmd/go/internal/modcmd/init.go b/src/cmd/go/internal/modcmd/init.go
index 356a0569913..618c673bf86 100644
--- a/src/cmd/go/internal/modcmd/init.go
+++ b/src/cmd/go/internal/modcmd/init.go
@@ -43,6 +43,6 @@ func runInit(ctx context.Context, cmd *base.Command, args []string) {
modPath = args[0]
}
- modload.ForceUseModules = true
+ modload.LoaderState.ForceUseModules = true
modload.CreateModFile(ctx, modPath) // does all the hard work
}
diff --git a/src/cmd/go/internal/modcmd/tidy.go b/src/cmd/go/internal/modcmd/tidy.go
index 2efa33a7c34..dde70c6d741 100644
--- a/src/cmd/go/internal/modcmd/tidy.go
+++ b/src/cmd/go/internal/modcmd/tidy.go
@@ -119,7 +119,7 @@ func runTidy(ctx context.Context, cmd *base.Command, args []string) {
// those packages. In order to make 'go test' reproducible for the packages
// that are in 'all' but outside of the main module, we must explicitly
// request that their test dependencies be included.
- modload.ForceUseModules = true
+ modload.LoaderState.ForceUseModules = true
modload.RootMode = modload.NeedRoot
goVersion := tidyGo.String()
diff --git a/src/cmd/go/internal/modcmd/vendor.go b/src/cmd/go/internal/modcmd/vendor.go
index e1a9081a95f..bd3d8d602e2 100644
--- a/src/cmd/go/internal/modcmd/vendor.go
+++ b/src/cmd/go/internal/modcmd/vendor.go
@@ -77,7 +77,7 @@ func RunVendor(ctx context.Context, vendorE bool, vendorO string, args []string)
if len(args) != 0 {
base.Fatalf("go: 'go mod vendor' accepts no arguments")
}
- modload.ForceUseModules = true
+ modload.LoaderState.ForceUseModules = true
modload.RootMode = modload.NeedRoot
loadOpts := modload.PackageOpts{
diff --git a/src/cmd/go/internal/modcmd/verify.go b/src/cmd/go/internal/modcmd/verify.go
index d07f730c5d0..ecd25d3a40e 100644
--- a/src/cmd/go/internal/modcmd/verify.go
+++ b/src/cmd/go/internal/modcmd/verify.go
@@ -50,7 +50,7 @@ func runVerify(ctx context.Context, cmd *base.Command, args []string) {
// NOTE(rsc): Could take a module pattern.
base.Fatalf("go: verify takes no arguments")
}
- modload.ForceUseModules = true
+ modload.LoaderState.ForceUseModules = true
modload.RootMode = modload.NeedRoot
// Only verify up to GOMAXPROCS zips at once.
diff --git a/src/cmd/go/internal/modcmd/why.go b/src/cmd/go/internal/modcmd/why.go
index 198672d8064..6c4bf8abab1 100644
--- a/src/cmd/go/internal/modcmd/why.go
+++ b/src/cmd/go/internal/modcmd/why.go
@@ -64,7 +64,7 @@ func init() {
func runWhy(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
- modload.ForceUseModules = true
+ modload.LoaderState.ForceUseModules = true
modload.RootMode = modload.NeedRoot
modload.ExplicitWriteGoMod = true // don't write go.mod in ListModules
diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go
index 25dbf3972fd..167f515be98 100644
--- a/src/cmd/go/internal/modget/get.go
+++ b/src/cmd/go/internal/modget/get.go
@@ -298,7 +298,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
base.Fatalf("go: -insecure flag is no longer supported; use GOINSECURE instead")
}
- modload.ForceUseModules = true
+ modload.LoaderState.ForceUseModules = true
// Do not allow any updating of go.mod until we've applied
// all the requested changes and checked that the result matches
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index d7d532ec942..264c02ef6db 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -41,10 +41,6 @@ var (
// RootMode determines whether a module root is needed.
RootMode Root
- // ForceUseModules may be set to force modules to be enabled when
- // GO111MODULE=auto or to report an error when GO111MODULE=off.
- ForceUseModules bool
-
allowMissingModuleImports bool
// ExplicitWriteGoMod prevents LoadPackages, ListModules, and other functions
@@ -96,7 +92,7 @@ func EnterWorkspace(ctx context.Context) (exit func(), err error) {
// Reset the state to a clean state.
oldstate := setState(State{})
- ForceUseModules = true
+ LoaderState.ForceUseModules = true
// Load in workspace mode.
InitWorkfile()
@@ -406,7 +402,7 @@ func Reset() {
func setState(s State) State {
oldState := State{
initialized: LoaderState.initialized,
- forceUseModules: ForceUseModules,
+ ForceUseModules: LoaderState.ForceUseModules,
rootMode: RootMode,
modRoots: modRoots,
modulesEnabled: cfg.ModulesEnabled,
@@ -414,7 +410,7 @@ func setState(s State) State {
requirements: requirements,
}
LoaderState.initialized = s.initialized
- ForceUseModules = s.forceUseModules
+ LoaderState.ForceUseModules = s.ForceUseModules
RootMode = s.rootMode
modRoots = s.modRoots
cfg.ModulesEnabled = s.modulesEnabled
@@ -429,8 +425,11 @@ func setState(s State) State {
}
type State struct {
- initialized bool
- forceUseModules bool
+ initialized bool
+
+ // ForceUseModules may be set to force modules to be enabled when
+ // GO111MODULE=auto or to report an error when GO111MODULE=off.
+ ForceUseModules bool
rootMode Root
modRoots []string
modulesEnabled bool
@@ -465,11 +464,11 @@ func Init() {
default:
base.Fatalf("go: unknown environment setting GO111MODULE=%s", env)
case "auto":
- mustUseModules = ForceUseModules
+ mustUseModules = LoaderState.ForceUseModules
case "on", "":
mustUseModules = true
case "off":
- if ForceUseModules {
+ if LoaderState.ForceUseModules {
base.Fatalf("go: modules disabled by GO111MODULE=off; see 'go help modules'")
}
mustUseModules = false
diff --git a/src/cmd/go/internal/run/run.go b/src/cmd/go/internal/run/run.go
index b81b1a007bd..05ea5eaa15e 100644
--- a/src/cmd/go/internal/run/run.go
+++ b/src/cmd/go/internal/run/run.go
@@ -76,7 +76,7 @@ func runRun(ctx context.Context, cmd *base.Command, args []string) {
// This must be done before modload.Init, but we need to call work.BuildInit
// before loading packages, since it affects package locations, e.g.,
// for -race and -msan.
- modload.ForceUseModules = true
+ modload.LoaderState.ForceUseModules = true
modload.RootMode = modload.NoRoot
modload.AllowMissingModuleImports()
modload.Init()
diff --git a/src/cmd/go/internal/toolchain/select.go b/src/cmd/go/internal/toolchain/select.go
index e8712613366..8f55076c628 100644
--- a/src/cmd/go/internal/toolchain/select.go
+++ b/src/cmd/go/internal/toolchain/select.go
@@ -353,7 +353,7 @@ func Exec(gotoolchain string) {
// Set up modules without an explicit go.mod, to download distribution.
modload.Reset()
- modload.ForceUseModules = true
+ modload.LoaderState.ForceUseModules = true
modload.RootMode = modload.NoRoot
modload.Init()
@@ -692,7 +692,7 @@ func maybeSwitchForGoInstallVersion(minVers string) {
// command lines if we add new flags in the future.
// Set up modules without an explicit go.mod, to download go.mod.
- modload.ForceUseModules = true
+ modload.LoaderState.ForceUseModules = true
modload.RootMode = modload.NoRoot
modload.Init()
defer modload.Reset()
diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go
index 6741b39f051..21bbab1bf47 100644
--- a/src/cmd/go/internal/work/build.go
+++ b/src/cmd/go/internal/work/build.go
@@ -859,7 +859,7 @@ func InstallPackages(ctx context.Context, patterns []string, pkgs []*load.Packag
//
// See golang.org/issue/40276 for details and rationale.
func installOutsideModule(ctx context.Context, args []string) {
- modload.ForceUseModules = true
+ modload.LoaderState.ForceUseModules = true
modload.RootMode = modload.NoRoot
modload.AllowMissingModuleImports()
modload.Init()
diff --git a/src/cmd/go/internal/workcmd/init.go b/src/cmd/go/internal/workcmd/init.go
index 02240b8189f..52185391c11 100644
--- a/src/cmd/go/internal/workcmd/init.go
+++ b/src/cmd/go/internal/workcmd/init.go
@@ -46,7 +46,7 @@ func init() {
func runInit(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
- modload.ForceUseModules = true
+ modload.LoaderState.ForceUseModules = true
gowork := modload.WorkFilePath()
if gowork == "" {
diff --git a/src/cmd/go/internal/workcmd/sync.go b/src/cmd/go/internal/workcmd/sync.go
index 719cf76c9bf..800dd15dd6f 100644
--- a/src/cmd/go/internal/workcmd/sync.go
+++ b/src/cmd/go/internal/workcmd/sync.go
@@ -48,7 +48,7 @@ func init() {
}
func runSync(ctx context.Context, cmd *base.Command, args []string) {
- modload.ForceUseModules = true
+ modload.LoaderState.ForceUseModules = true
modload.InitWorkfile()
if modload.WorkFilePath() == "" {
base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
diff --git a/src/cmd/go/internal/workcmd/use.go b/src/cmd/go/internal/workcmd/use.go
index afbe99d3a48..28421635178 100644
--- a/src/cmd/go/internal/workcmd/use.go
+++ b/src/cmd/go/internal/workcmd/use.go
@@ -61,7 +61,7 @@ func init() {
}
func runUse(ctx context.Context, cmd *base.Command, args []string) {
- modload.ForceUseModules = true
+ modload.LoaderState.ForceUseModules = true
modload.InitWorkfile()
gowork := modload.WorkFilePath()
if gowork == "" {
From 2e52060084ff170097347457525f0debde91aea9 Mon Sep 17 00:00:00 2001
From: Ian Alexander
Date: Wed, 20 Aug 2025 19:34:40 -0400
Subject: [PATCH 034/421] cmd/go: refactor usage of `RootMode`
This commit refactors usage of the global variable `RootMode` to the
global LoaderState variable of the same name.
This commit is part of the overall effort to eliminate global
modloader state.
[git-generate]
cd src/cmd/go/internal/modload
rf 'mv State.rootMode State.RootMode'
for dir in load modcmd run tool toolchain work ; do
cd ../${dir}
rf 'ex {
import "cmd/go/internal/modload";
modload.RootMode -> modload.LoaderState.RootMode
}'
done
cd ../modload
rf 'ex { RootMode -> LoaderState.RootMode }'
rf 'add State.ForceUseModules \
// RootMode determines whether a module root is needed.'
rf 'rm RootMode'
Change-Id: Ib5e513ee570dfc3b01cc974fe32944e5e391fd82
Reviewed-on: https://go-review.googlesource.com/c/go/+/698058
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Michael Matloob
Reviewed-by: Michael Matloob
---
src/cmd/go/internal/load/godebug.go | 2 +-
src/cmd/go/internal/load/pkg.go | 2 +-
src/cmd/go/internal/modcmd/graph.go | 2 +-
src/cmd/go/internal/modcmd/tidy.go | 2 +-
src/cmd/go/internal/modcmd/vendor.go | 2 +-
src/cmd/go/internal/modcmd/verify.go | 2 +-
src/cmd/go/internal/modcmd/why.go | 2 +-
src/cmd/go/internal/modload/import_test.go | 6 ++--
src/cmd/go/internal/modload/init.go | 33 +++++++++++-----------
src/cmd/go/internal/run/run.go | 2 +-
src/cmd/go/internal/tool/tool.go | 2 +-
src/cmd/go/internal/toolchain/select.go | 4 +--
src/cmd/go/internal/work/build.go | 2 +-
13 files changed, 31 insertions(+), 32 deletions(-)
diff --git a/src/cmd/go/internal/load/godebug.go b/src/cmd/go/internal/load/godebug.go
index 8ea8ffab1ae..ff184384567 100644
--- a/src/cmd/go/internal/load/godebug.go
+++ b/src/cmd/go/internal/load/godebug.go
@@ -50,7 +50,7 @@ func defaultGODEBUG(p *Package, directives, testDirectives, xtestDirectives []bu
return ""
}
goVersion := modload.MainModules.GoVersion()
- if modload.RootMode == modload.NoRoot && p.Module != nil {
+ if modload.LoaderState.RootMode == modload.NoRoot && p.Module != nil {
// This is go install pkg@version or go run pkg@version.
// Use the Go version from the package.
// If there isn't one, then assume Go 1.20,
diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go
index 48b2e70d74b..27a1fbfff83 100644
--- a/src/cmd/go/internal/load/pkg.go
+++ b/src/cmd/go/internal/load/pkg.go
@@ -3351,7 +3351,7 @@ func PackagesAndErrorsOutsideModule(ctx context.Context, opts PackageOpts, args
if !modload.LoaderState.ForceUseModules {
panic("modload.ForceUseModules must be true")
}
- if modload.RootMode != modload.NoRoot {
+ if modload.LoaderState.RootMode != modload.NoRoot {
panic("modload.RootMode must be NoRoot")
}
diff --git a/src/cmd/go/internal/modcmd/graph.go b/src/cmd/go/internal/modcmd/graph.go
index 4abae33129a..5f47260e188 100644
--- a/src/cmd/go/internal/modcmd/graph.go
+++ b/src/cmd/go/internal/modcmd/graph.go
@@ -58,7 +58,7 @@ func runGraph(ctx context.Context, cmd *base.Command, args []string) {
base.Fatalf("go: 'go mod graph' accepts no arguments")
}
modload.LoaderState.ForceUseModules = true
- modload.RootMode = modload.NeedRoot
+ modload.LoaderState.RootMode = modload.NeedRoot
goVersion := graphGo.String()
if goVersion != "" && gover.Compare(gover.Local(), goVersion) < 0 {
diff --git a/src/cmd/go/internal/modcmd/tidy.go b/src/cmd/go/internal/modcmd/tidy.go
index dde70c6d741..0314dcef250 100644
--- a/src/cmd/go/internal/modcmd/tidy.go
+++ b/src/cmd/go/internal/modcmd/tidy.go
@@ -120,7 +120,7 @@ func runTidy(ctx context.Context, cmd *base.Command, args []string) {
// that are in 'all' but outside of the main module, we must explicitly
// request that their test dependencies be included.
modload.LoaderState.ForceUseModules = true
- modload.RootMode = modload.NeedRoot
+ modload.LoaderState.RootMode = modload.NeedRoot
goVersion := tidyGo.String()
if goVersion != "" && gover.Compare(gover.Local(), goVersion) < 0 {
diff --git a/src/cmd/go/internal/modcmd/vendor.go b/src/cmd/go/internal/modcmd/vendor.go
index bd3d8d602e2..dfea571c0e0 100644
--- a/src/cmd/go/internal/modcmd/vendor.go
+++ b/src/cmd/go/internal/modcmd/vendor.go
@@ -78,7 +78,7 @@ func RunVendor(ctx context.Context, vendorE bool, vendorO string, args []string)
base.Fatalf("go: 'go mod vendor' accepts no arguments")
}
modload.LoaderState.ForceUseModules = true
- modload.RootMode = modload.NeedRoot
+ modload.LoaderState.RootMode = modload.NeedRoot
loadOpts := modload.PackageOpts{
Tags: imports.AnyTags(),
diff --git a/src/cmd/go/internal/modcmd/verify.go b/src/cmd/go/internal/modcmd/verify.go
index ecd25d3a40e..157c920c067 100644
--- a/src/cmd/go/internal/modcmd/verify.go
+++ b/src/cmd/go/internal/modcmd/verify.go
@@ -51,7 +51,7 @@ func runVerify(ctx context.Context, cmd *base.Command, args []string) {
base.Fatalf("go: verify takes no arguments")
}
modload.LoaderState.ForceUseModules = true
- modload.RootMode = modload.NeedRoot
+ modload.LoaderState.RootMode = modload.NeedRoot
// Only verify up to GOMAXPROCS zips at once.
type token struct{}
diff --git a/src/cmd/go/internal/modcmd/why.go b/src/cmd/go/internal/modcmd/why.go
index 6c4bf8abab1..62a5387ed88 100644
--- a/src/cmd/go/internal/modcmd/why.go
+++ b/src/cmd/go/internal/modcmd/why.go
@@ -65,7 +65,7 @@ func init() {
func runWhy(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
modload.LoaderState.ForceUseModules = true
- modload.RootMode = modload.NeedRoot
+ modload.LoaderState.RootMode = modload.NeedRoot
modload.ExplicitWriteGoMod = true // don't write go.mod in ListModules
loadOpts := modload.PackageOpts{
diff --git a/src/cmd/go/internal/modload/import_test.go b/src/cmd/go/internal/modload/import_test.go
index eb4f5d64d3a..f6b8bb90992 100644
--- a/src/cmd/go/internal/modload/import_test.go
+++ b/src/cmd/go/internal/modload/import_test.go
@@ -60,13 +60,13 @@ func TestQueryImport(t *testing.T) {
testenv.MustHaveExecPath(t, "git")
oldAllowMissingModuleImports := allowMissingModuleImports
- oldRootMode := RootMode
+ oldRootMode := LoaderState.RootMode
defer func() {
allowMissingModuleImports = oldAllowMissingModuleImports
- RootMode = oldRootMode
+ LoaderState.RootMode = oldRootMode
}()
allowMissingModuleImports = true
- RootMode = NoRoot
+ LoaderState.RootMode = NoRoot
ctx := context.Background()
rs := LoadModFile(ctx)
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 264c02ef6db..58b16a6599d 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -38,9 +38,6 @@ import (
//
// TODO(#40775): See if these can be plumbed as explicit parameters.
var (
- // RootMode determines whether a module root is needed.
- RootMode Root
-
allowMissingModuleImports bool
// ExplicitWriteGoMod prevents LoadPackages, ListModules, and other functions
@@ -370,7 +367,7 @@ func InitWorkfile() {
// It is exported mainly for Go toolchain switching, which must process
// the go.work very early at startup.
func FindGoWork(wd string) string {
- if RootMode == NoRoot {
+ if LoaderState.RootMode == NoRoot {
return ""
}
@@ -403,7 +400,7 @@ func setState(s State) State {
oldState := State{
initialized: LoaderState.initialized,
ForceUseModules: LoaderState.ForceUseModules,
- rootMode: RootMode,
+ RootMode: LoaderState.RootMode,
modRoots: modRoots,
modulesEnabled: cfg.ModulesEnabled,
mainModules: MainModules,
@@ -411,7 +408,7 @@ func setState(s State) State {
}
LoaderState.initialized = s.initialized
LoaderState.ForceUseModules = s.ForceUseModules
- RootMode = s.rootMode
+ LoaderState.RootMode = s.RootMode
modRoots = s.modRoots
cfg.ModulesEnabled = s.modulesEnabled
MainModules = s.mainModules
@@ -430,13 +427,15 @@ type State struct {
// ForceUseModules may be set to force modules to be enabled when
// GO111MODULE=auto or to report an error when GO111MODULE=off.
ForceUseModules bool
- rootMode Root
- modRoots []string
- modulesEnabled bool
- mainModules *MainModuleSet
- requirements *Requirements
- workFilePath string
- modfetchState modfetch.State
+
+ // RootMode determines whether a module root is needed.
+ RootMode Root
+ modRoots []string
+ modulesEnabled bool
+ mainModules *MainModuleSet
+ requirements *Requirements
+ workFilePath string
+ modfetchState modfetch.State
}
func NewState() *State { return &State{} }
@@ -495,7 +494,7 @@ func Init() {
if modRoots != nil {
// modRoot set before Init was called ("go mod init" does this).
// No need to search for go.mod.
- } else if RootMode == NoRoot {
+ } else if LoaderState.RootMode == NoRoot {
if cfg.ModFile != "" && !base.InGOFLAGS("-modfile") {
base.Fatalf("go: -modfile cannot be used with commands that ignore the current module")
}
@@ -510,7 +509,7 @@ func Init() {
if cfg.ModFile != "" {
base.Fatalf("go: cannot find main module, but -modfile was set.\n\t-modfile cannot be used to set the module root directory.")
}
- if RootMode == NeedRoot {
+ if LoaderState.RootMode == NeedRoot {
base.Fatal(ErrNoModRoot)
}
if !mustUseModules {
@@ -525,7 +524,7 @@ func Init() {
// It's a bit of a peculiar thing to disallow but quite mysterious
// when it happens. See golang.org/issue/26708.
fmt.Fprintf(os.Stderr, "go: warning: ignoring go.mod in system temp root %v\n", os.TempDir())
- if RootMode == NeedRoot {
+ if LoaderState.RootMode == NeedRoot {
base.Fatal(ErrNoModRoot)
}
if !mustUseModules {
@@ -547,7 +546,7 @@ func Init() {
gopath = list[0]
if _, err := fsys.Stat(filepath.Join(gopath, "go.mod")); err == nil {
fmt.Fprintf(os.Stderr, "go: warning: ignoring go.mod in $GOPATH %v\n", gopath)
- if RootMode == NeedRoot {
+ if LoaderState.RootMode == NeedRoot {
base.Fatal(ErrNoModRoot)
}
if !mustUseModules {
diff --git a/src/cmd/go/internal/run/run.go b/src/cmd/go/internal/run/run.go
index 05ea5eaa15e..d922dcdd66a 100644
--- a/src/cmd/go/internal/run/run.go
+++ b/src/cmd/go/internal/run/run.go
@@ -77,7 +77,7 @@ func runRun(ctx context.Context, cmd *base.Command, args []string) {
// before loading packages, since it affects package locations, e.g.,
// for -race and -msan.
modload.LoaderState.ForceUseModules = true
- modload.RootMode = modload.NoRoot
+ modload.LoaderState.RootMode = modload.NoRoot
modload.AllowMissingModuleImports()
modload.Init()
} else {
diff --git a/src/cmd/go/internal/tool/tool.go b/src/cmd/go/internal/tool/tool.go
index 120ef5339be..ef25d17b54d 100644
--- a/src/cmd/go/internal/tool/tool.go
+++ b/src/cmd/go/internal/tool/tool.go
@@ -308,7 +308,7 @@ func buildAndRunBuiltinTool(ctx context.Context, toolName, tool string, args []s
// Ignore go.mod and go.work: we don't need them, and we want to be able
// to run the tool even if there's an issue with the module or workspace the
// user happens to be in.
- modload.RootMode = modload.NoRoot
+ modload.LoaderState.RootMode = modload.NoRoot
runFunc := func(b *work.Builder, ctx context.Context, a *work.Action) error {
cmdline := str.StringList(builtTool(a), a.Args)
diff --git a/src/cmd/go/internal/toolchain/select.go b/src/cmd/go/internal/toolchain/select.go
index 8f55076c628..4f46b19c121 100644
--- a/src/cmd/go/internal/toolchain/select.go
+++ b/src/cmd/go/internal/toolchain/select.go
@@ -354,7 +354,7 @@ func Exec(gotoolchain string) {
// Set up modules without an explicit go.mod, to download distribution.
modload.Reset()
modload.LoaderState.ForceUseModules = true
- modload.RootMode = modload.NoRoot
+ modload.LoaderState.RootMode = modload.NoRoot
modload.Init()
// Download and unpack toolchain module into module cache.
@@ -693,7 +693,7 @@ func maybeSwitchForGoInstallVersion(minVers string) {
// Set up modules without an explicit go.mod, to download go.mod.
modload.LoaderState.ForceUseModules = true
- modload.RootMode = modload.NoRoot
+ modload.LoaderState.RootMode = modload.NoRoot
modload.Init()
defer modload.Reset()
diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go
index 21bbab1bf47..adc98f93138 100644
--- a/src/cmd/go/internal/work/build.go
+++ b/src/cmd/go/internal/work/build.go
@@ -860,7 +860,7 @@ func InstallPackages(ctx context.Context, patterns []string, pkgs []*load.Packag
// See golang.org/issue/40276 for details and rationale.
func installOutsideModule(ctx context.Context, args []string) {
modload.LoaderState.ForceUseModules = true
- modload.RootMode = modload.NoRoot
+ modload.LoaderState.RootMode = modload.NoRoot
modload.AllowMissingModuleImports()
modload.Init()
BuildInit()
From 11d5484190f80823c9b6312fd40f6491e864111b Mon Sep 17 00:00:00 2001
From: Michael Anthony Knyszek
Date: Tue, 7 Oct 2025 16:10:19 +0000
Subject: [PATCH 035/421] runtime: fix self-deadlock on sbrk platforms
The sbrk mem.go implementation doesn't enforce being called on the
systemstack, but it can call back into itself if there's a stack growth.
Because the sbrk implementation requires acquiring memlock, it can
self-deadlock.
For the most part the mem.go API is called on the system stack, but
there are cases where we call sysAlloc on the regular Go stack. This is
fine in general, except on sbrk platforms because of the aforementioned
deadlock.
This change, rather than adding a new invariant to mem.go, switches to
the systemstack in the mem.go API implementation for sbrk platforms.
Change-Id: Ie0f0ea80a8d7578cdeabc8252107e64a5e633856
Reviewed-on: https://go-review.googlesource.com/c/go/+/709775
Reviewed-by: Cherry Mui
LUCI-TryBot-Result: Go LUCI
---
src/runtime/mem_sbrk.go | 160 ++++++++++++++++++++++++++--------------
1 file changed, 104 insertions(+), 56 deletions(-)
diff --git a/src/runtime/mem_sbrk.go b/src/runtime/mem_sbrk.go
index 05f0fdb5d74..5284bbd0009 100644
--- a/src/runtime/mem_sbrk.go
+++ b/src/runtime/mem_sbrk.go
@@ -48,6 +48,16 @@ type memHdrPtr uintptr
func (p memHdrPtr) ptr() *memHdr { return (*memHdr)(unsafe.Pointer(p)) }
func (p *memHdrPtr) set(x *memHdr) { *p = memHdrPtr(unsafe.Pointer(x)) }
+// memAlloc allocates n bytes from the brk reservation, or if it's full,
+// the system.
+//
+// memlock must be held.
+//
+// memAlloc must be called on the system stack, otherwise a stack growth
+// could cause us to call back into it. Since memlock is held, that could
+// lead to a self-deadlock.
+//
+//go:systemstack
func memAlloc(n uintptr) unsafe.Pointer {
if p := memAllocNoGrow(n); p != nil {
return p
@@ -55,6 +65,15 @@ func memAlloc(n uintptr) unsafe.Pointer {
return sbrk(n)
}
+// memAllocNoGrow attempts to allocate n bytes from the existing brk.
+//
+// memlock must be held.
+//
+// memAlloc must be called on the system stack, otherwise a stack growth
+// could cause us to call back into it. Since memlock is held, that could
+// lead to a self-deadlock.
+//
+//go:systemstack
func memAllocNoGrow(n uintptr) unsafe.Pointer {
n = memRound(n)
var prevp *memHdr
@@ -78,6 +97,15 @@ func memAllocNoGrow(n uintptr) unsafe.Pointer {
return nil
}
+// memFree makes [ap, ap+n) available for reallocation by memAlloc.
+//
+// memlock must be held.
+//
+// memAlloc must be called on the system stack, otherwise a stack growth
+// could cause us to call back into it. Since memlock is held, that could
+// lead to a self-deadlock.
+//
+//go:systemstack
func memFree(ap unsafe.Pointer, n uintptr) {
n = memRound(n)
memclrNoHeapPointers(ap, n)
@@ -122,6 +150,15 @@ func memFree(ap unsafe.Pointer, n uintptr) {
}
}
+// memCheck checks invariants around free list management.
+//
+// memlock must be held.
+//
+// memAlloc must be called on the system stack, otherwise a stack growth
+// could cause us to call back into it. Since memlock is held, that could
+// lead to a self-deadlock.
+//
+//go:systemstack
func memCheck() {
if !memDebug {
return
@@ -158,26 +195,31 @@ func initBloc() {
}
func sysAllocOS(n uintptr, _ string) unsafe.Pointer {
- lock(&memlock)
- p := memAlloc(n)
- memCheck()
- unlock(&memlock)
- return p
+ var p uintptr
+ systemstack(func() {
+ lock(&memlock)
+ p = uintptr(memAlloc(n))
+ memCheck()
+ unlock(&memlock)
+ })
+ return unsafe.Pointer(p)
}
func sysFreeOS(v unsafe.Pointer, n uintptr) {
- lock(&memlock)
- if uintptr(v)+n == bloc {
- // Address range being freed is at the end of memory,
- // so record a new lower value for end of memory.
- // Can't actually shrink address space because segment is shared.
- memclrNoHeapPointers(v, n)
- bloc -= n
- } else {
- memFree(v, n)
- memCheck()
- }
- unlock(&memlock)
+ systemstack(func() {
+ lock(&memlock)
+ if uintptr(v)+n == bloc {
+ // Address range being freed is at the end of memory,
+ // so record a new lower value for end of memory.
+ // Can't actually shrink address space because segment is shared.
+ memclrNoHeapPointers(v, n)
+ bloc -= n
+ } else {
+ memFree(v, n)
+ memCheck()
+ }
+ unlock(&memlock)
+ })
}
func sysUnusedOS(v unsafe.Pointer, n uintptr) {
@@ -202,49 +244,55 @@ func sysFaultOS(v unsafe.Pointer, n uintptr) {
}
func sysReserveOS(v unsafe.Pointer, n uintptr, _ string) unsafe.Pointer {
- lock(&memlock)
- var p unsafe.Pointer
- if uintptr(v) == bloc {
- // Address hint is the current end of memory,
- // so try to extend the address space.
- p = sbrk(n)
- }
- if p == nil && v == nil {
- p = memAlloc(n)
- memCheck()
- }
- unlock(&memlock)
- return p
+ var p uintptr
+ systemstack(func() {
+ lock(&memlock)
+ if uintptr(v) == bloc {
+ // Address hint is the current end of memory,
+ // so try to extend the address space.
+ p = uintptr(sbrk(n))
+ }
+ if p == 0 && v == nil {
+ p = uintptr(memAlloc(n))
+ memCheck()
+ }
+ unlock(&memlock)
+ })
+ return unsafe.Pointer(p)
}
func sysReserveAlignedSbrk(size, align uintptr) (unsafe.Pointer, uintptr) {
- lock(&memlock)
- if p := memAllocNoGrow(size + align); p != nil {
- // We can satisfy the reservation from the free list.
- // Trim off the unaligned parts.
- pAligned := alignUp(uintptr(p), align)
- if startLen := pAligned - uintptr(p); startLen > 0 {
- memFree(p, startLen)
+ var p uintptr
+ systemstack(func() {
+ lock(&memlock)
+ if base := memAllocNoGrow(size + align); base != nil {
+ // We can satisfy the reservation from the free list.
+ // Trim off the unaligned parts.
+ start := alignUp(uintptr(base), align)
+ if startLen := start - uintptr(base); startLen > 0 {
+ memFree(base, startLen)
+ }
+ end := start + size
+ if endLen := (uintptr(base) + size + align) - end; endLen > 0 {
+ memFree(unsafe.Pointer(end), endLen)
+ }
+ memCheck()
+ unlock(&memlock)
+ p = start
+ return
}
- end := pAligned + size
- if endLen := (uintptr(p) + size + align) - end; endLen > 0 {
- memFree(unsafe.Pointer(end), endLen)
- }
- memCheck()
- unlock(&memlock)
- return unsafe.Pointer(pAligned), size
- }
- // Round up bloc to align, then allocate size.
- p := alignUp(bloc, align)
- r := sbrk(p + size - bloc)
- if r == nil {
- p, size = 0, 0
- } else if l := p - uintptr(r); l > 0 {
- // Free the area we skipped over for alignment.
- memFree(r, l)
- memCheck()
- }
- unlock(&memlock)
+ // Round up bloc to align, then allocate size.
+ p = alignUp(bloc, align)
+ r := sbrk(p + size - bloc)
+ if r == nil {
+ p, size = 0, 0
+ } else if l := p - uintptr(r); l > 0 {
+ // Free the area we skipped over for alignment.
+ memFree(r, l)
+ memCheck()
+ }
+ unlock(&memlock)
+ })
return unsafe.Pointer(p), size
}
From 6f7926589d03180863aa05cbb55a9d9c63e76b99 Mon Sep 17 00:00:00 2001
From: Ian Alexander
Date: Wed, 20 Aug 2025 19:44:11 -0400
Subject: [PATCH 036/421] cmd/go: refactor usage of `modRoots`
This commit refactors usage of the global variable `modRoots` to the
global LoaderState field of the same name.
This commit is part of the overall effort to eliminate global
modloader state.
[git-generate]
cd src/cmd/go/internal/modload
rf 'ex { modRoots -> LoaderState.modRoots }'
rf 'add State.RootMode \
// These are primarily used to initialize the MainModules, and should\
// be eventually superseded by them but are still used in cases where\
// the module roots are required but MainModules has not been\
// initialized yet. Set to the modRoots of the main modules.\
// modRoots != nil implies len(modRoots) > 0'
rf 'rm modRoots'
Change-Id: Ie9e1f3d468cfceee25efefaf945b10492318b079
Reviewed-on: https://go-review.googlesource.com/c/go/+/698059
Reviewed-by: Michael Matloob
Reviewed-by: Michael Matloob
LUCI-TryBot-Result: Go LUCI
---
src/cmd/go/internal/modload/init.go | 61 ++++++++++++++--------------
src/cmd/go/internal/modload/load.go | 2 +-
src/cmd/go/internal/modload/query.go | 2 +-
3 files changed, 32 insertions(+), 33 deletions(-)
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 58b16a6599d..dc0d78499b9 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -53,14 +53,7 @@ var (
// Variables set in Init.
var (
-
- // These are primarily used to initialize the MainModules, and should be
- // eventually superseded by them but are still used in cases where the module
- // roots are required but MainModules hasn't been initialized yet. Set to
- // the modRoots of the main modules.
- // modRoots != nil implies len(modRoots) > 0
- modRoots []string
- gopath string
+ gopath string
)
// EnterModule resets MainModules and requirements to refer to just this one module.
@@ -70,7 +63,7 @@ func EnterModule(ctx context.Context, enterModroot string) {
workFilePath = "" // Force module mode
modfetch.Reset()
- modRoots = []string{enterModroot}
+ LoaderState.modRoots = []string{enterModroot}
LoadModFile(ctx)
}
@@ -401,7 +394,7 @@ func setState(s State) State {
initialized: LoaderState.initialized,
ForceUseModules: LoaderState.ForceUseModules,
RootMode: LoaderState.RootMode,
- modRoots: modRoots,
+ modRoots: LoaderState.modRoots,
modulesEnabled: cfg.ModulesEnabled,
mainModules: MainModules,
requirements: requirements,
@@ -409,7 +402,7 @@ func setState(s State) State {
LoaderState.initialized = s.initialized
LoaderState.ForceUseModules = s.ForceUseModules
LoaderState.RootMode = s.RootMode
- modRoots = s.modRoots
+ LoaderState.modRoots = s.modRoots
cfg.ModulesEnabled = s.modulesEnabled
MainModules = s.mainModules
requirements = s.requirements
@@ -429,7 +422,13 @@ type State struct {
ForceUseModules bool
// RootMode determines whether a module root is needed.
- RootMode Root
+ RootMode Root
+
+ // These are primarily used to initialize the MainModules, and should
+ // be eventually superseded by them but are still used in cases where
+ // the module roots are required but MainModules has not been
+ // initialized yet. Set to the modRoots of the main modules.
+ // modRoots != nil implies len(modRoots) > 0
modRoots []string
modulesEnabled bool
mainModules *MainModuleSet
@@ -491,14 +490,14 @@ func Init() {
if os.Getenv("GCM_INTERACTIVE") == "" {
os.Setenv("GCM_INTERACTIVE", "never")
}
- if modRoots != nil {
+ if LoaderState.modRoots != nil {
// modRoot set before Init was called ("go mod init" does this).
// No need to search for go.mod.
} else if LoaderState.RootMode == NoRoot {
if cfg.ModFile != "" && !base.InGOFLAGS("-modfile") {
base.Fatalf("go: -modfile cannot be used with commands that ignore the current module")
}
- modRoots = nil
+ LoaderState.modRoots = nil
} else if workFilePath != "" {
// We're in workspace mode, which implies module mode.
if cfg.ModFile != "" {
@@ -531,7 +530,7 @@ func Init() {
return
}
} else {
- modRoots = []string{modRoot}
+ LoaderState.modRoots = []string{modRoot}
}
}
if cfg.ModFile != "" && !strings.HasSuffix(cfg.ModFile, ".mod") {
@@ -566,7 +565,7 @@ func Init() {
// be called until the command is installed and flags are parsed. Instead of
// calling Init and Enabled, the main package can call this function.
func WillBeEnabled() bool {
- if modRoots != nil || cfg.ModulesEnabled {
+ if LoaderState.modRoots != nil || cfg.ModulesEnabled {
// Already enabled.
return true
}
@@ -619,7 +618,7 @@ func FindGoMod(wd string) string {
// (usually through MustModRoot).
func Enabled() bool {
Init()
- return modRoots != nil || cfg.ModulesEnabled
+ return LoaderState.modRoots != nil || cfg.ModulesEnabled
}
func VendorDir() string {
@@ -651,7 +650,7 @@ func inWorkspaceMode() bool {
// does not require a main module.
func HasModRoot() bool {
Init()
- return modRoots != nil
+ return LoaderState.modRoots != nil
}
// MustHaveModRoot checks that a main module or main modules are present,
@@ -880,16 +879,16 @@ func loadModFile(ctx context.Context, opts *PackageOpts) (*Requirements, error)
var workFile *modfile.WorkFile
if inWorkspaceMode() {
var err error
- workFile, modRoots, err = LoadWorkFile(workFilePath)
+ workFile, LoaderState.modRoots, err = LoadWorkFile(workFilePath)
if err != nil {
return nil, err
}
- for _, modRoot := range modRoots {
+ for _, modRoot := range LoaderState.modRoots {
sumFile := strings.TrimSuffix(modFilePath(modRoot), ".mod") + ".sum"
modfetch.WorkspaceGoSumFiles = append(modfetch.WorkspaceGoSumFiles, sumFile)
}
modfetch.GoSumFile = workFilePath + ".sum"
- } else if len(modRoots) == 0 {
+ } else if len(LoaderState.modRoots) == 0 {
// We're in module mode, but not inside a module.
//
// Commands like 'go build', 'go run', 'go list' have no go.mod file to
@@ -908,9 +907,9 @@ func loadModFile(ctx context.Context, opts *PackageOpts) (*Requirements, error)
//
// See golang.org/issue/32027.
} else {
- modfetch.GoSumFile = strings.TrimSuffix(modFilePath(modRoots[0]), ".mod") + ".sum"
+ modfetch.GoSumFile = strings.TrimSuffix(modFilePath(LoaderState.modRoots[0]), ".mod") + ".sum"
}
- if len(modRoots) == 0 {
+ if len(LoaderState.modRoots) == 0 {
// TODO(#49228): Instead of creating a fake module with an empty modroot,
// make MainModules.Len() == 0 mean that we're in module mode but not inside
// any module.
@@ -956,7 +955,7 @@ func loadModFile(ctx context.Context, opts *PackageOpts) (*Requirements, error)
var mainModules []module.Version
var indices []*modFileIndex
var errs []error
- for _, modroot := range modRoots {
+ for _, modroot := range LoaderState.modRoots {
gomod := modFilePath(modroot)
var fixed bool
data, f, err := ReadModFile(gomod, fixVersion(ctx, &fixed))
@@ -1017,7 +1016,7 @@ func loadModFile(ctx context.Context, opts *PackageOpts) (*Requirements, error)
return nil, errors.Join(errs...)
}
- MainModules = makeMainModules(mainModules, modRoots, modFiles, indices, workFile)
+ MainModules = makeMainModules(mainModules, LoaderState.modRoots, modFiles, indices, workFile)
setDefaultBuildMod() // possibly enable automatic vendoring
rs := requirementsFromModFiles(ctx, workFile, modFiles, opts)
@@ -1120,7 +1119,7 @@ func CheckReservedModulePath(path string) error {
// packages at multiple versions from the same module).
func CreateModFile(ctx context.Context, modPath string) {
modRoot := base.Cwd()
- modRoots = []string{modRoot}
+ LoaderState.modRoots = []string{modRoot}
Init()
modFilePath := modFilePath(modRoot)
if _, err := fsys.Stat(modFilePath); err == nil {
@@ -1509,7 +1508,7 @@ func setDefaultBuildMod() {
cfg.BuildMod = "readonly"
return
}
- if modRoots == nil {
+ if LoaderState.modRoots == nil {
if allowMissingModuleImports {
cfg.BuildMod = "mod"
} else {
@@ -1518,7 +1517,7 @@ func setDefaultBuildMod() {
return
}
- if len(modRoots) >= 1 {
+ if len(LoaderState.modRoots) >= 1 {
var goVersion string
var versionSource string
if inWorkspaceMode() {
@@ -1537,10 +1536,10 @@ func setDefaultBuildMod() {
if workFilePath != "" {
vendorDir = filepath.Join(filepath.Dir(workFilePath), "vendor")
} else {
- if len(modRoots) != 1 {
- panic(fmt.Errorf("outside workspace mode, but have %v modRoots", modRoots))
+ if len(LoaderState.modRoots) != 1 {
+ panic(fmt.Errorf("outside workspace mode, but have %v modRoots", LoaderState.modRoots))
}
- vendorDir = filepath.Join(modRoots[0], "vendor")
+ vendorDir = filepath.Join(LoaderState.modRoots[0], "vendor")
}
if fi, err := fsys.Stat(vendorDir); err == nil && fi.IsDir() {
if goVersion != "" {
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index 8b2be3b300e..7fba712f952 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -271,7 +271,7 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
case m.IsLocal():
// Evaluate list of file system directories on first iteration.
if m.Dirs == nil {
- matchModRoots := modRoots
+ matchModRoots := LoaderState.modRoots
if opts.MainModule != (module.Version{}) {
matchModRoots = []string{MainModules.ModRoot(opts.MainModule)}
}
diff --git a/src/cmd/go/internal/modload/query.go b/src/cmd/go/internal/modload/query.go
index c4cf55442ba..65934b0d69e 100644
--- a/src/cmd/go/internal/modload/query.go
+++ b/src/cmd/go/internal/modload/query.go
@@ -716,7 +716,7 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
var mainModuleMatches []module.Version
for _, mainModule := range MainModules.Versions() {
- m := match(mainModule, modRoots, true)
+ m := match(mainModule, LoaderState.modRoots, true)
if len(m.Pkgs) > 0 {
if query != "upgrade" && query != "patch" {
return nil, nil, &QueryMatchesPackagesInMainModuleError{
From 6e4007e8cffbb870e6b606307ab7308236ecefb9 Mon Sep 17 00:00:00 2001
From: Neal Patel
Date: Thu, 11 Sep 2025 16:27:04 -0400
Subject: [PATCH 037/421] crypto/x509: mitigate DoS vector when intermediate
certificate contains DSA public key
An attacker could craft an intermediate X.509 certificate
containing a DSA public key and can crash a remote host
with an unauthenticated call to any endpoint that
verifies the certificate chain.
Thank you to Jakub Ciolek for reporting this issue.
Fixes CVE-2025-58188
Fixes #75675
Change-Id: I2ecbb87b9b8268dbc55c8795891e596ab60f0088
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2780
Reviewed-by: Damien Neil
Reviewed-by: Roland Shoemaker
Reviewed-on: https://go-review.googlesource.com/c/go/+/709853
Reviewed-by: Carlos Amedee
Auto-Submit: Michael Pratt
LUCI-TryBot-Result: Go LUCI
---
src/crypto/x509/verify.go | 5 +-
src/crypto/x509/verify_test.go | 127 +++++++++++++++++++++++++++++++++
2 files changed, 131 insertions(+), 1 deletion(-)
diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go
index 7cc0fb2e3e0..755c1db96c1 100644
--- a/src/crypto/x509/verify.go
+++ b/src/crypto/x509/verify.go
@@ -927,7 +927,10 @@ func alreadyInChain(candidate *Certificate, chain []*Certificate) bool {
if !bytes.Equal(candidate.RawSubject, cert.RawSubject) {
continue
}
- if !candidate.PublicKey.(pubKeyEqual).Equal(cert.PublicKey) {
+ // We enforce the canonical encoding of SPKI (by only allowing the
+ // correct AI paremeter encodings in parseCertificate), so it's safe to
+ // directly compare the raw bytes.
+ if !bytes.Equal(candidate.RawSubjectPublicKeyInfo, cert.RawSubjectPublicKeyInfo) {
continue
}
var certSAN *pkix.Extension
diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go
index 7991f49946d..5595f99ea5e 100644
--- a/src/crypto/x509/verify_test.go
+++ b/src/crypto/x509/verify_test.go
@@ -6,6 +6,7 @@ package x509
import (
"crypto"
+ "crypto/dsa"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
@@ -3048,3 +3049,129 @@ func TestInvalidPolicyWithAnyKeyUsage(t *testing.T) {
t.Fatalf("unexpected error, got %q, want %q", err, expectedErr)
}
}
+
+func TestCertificateChainSignedByECDSA(t *testing.T) {
+ caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ root := &Certificate{
+ SerialNumber: big.NewInt(1),
+ Subject: pkix.Name{CommonName: "X"},
+ NotBefore: time.Now().Add(-time.Hour),
+ NotAfter: time.Now().Add(365 * 24 * time.Hour),
+ IsCA: true,
+ KeyUsage: KeyUsageCertSign | KeyUsageCRLSign,
+ BasicConstraintsValid: true,
+ }
+ caDER, err := CreateCertificate(rand.Reader, root, root, &caKey.PublicKey, caKey)
+ if err != nil {
+ t.Fatal(err)
+ }
+ root, err = ParseCertificate(caDER)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ leafKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ leaf := &Certificate{
+ SerialNumber: big.NewInt(42),
+ Subject: pkix.Name{CommonName: "leaf"},
+ NotBefore: time.Now().Add(-10 * time.Minute),
+ NotAfter: time.Now().Add(24 * time.Hour),
+ KeyUsage: KeyUsageDigitalSignature,
+ ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth},
+ BasicConstraintsValid: true,
+ }
+ leafDER, err := CreateCertificate(rand.Reader, leaf, root, &leafKey.PublicKey, caKey)
+ if err != nil {
+ t.Fatal(err)
+ }
+ leaf, err = ParseCertificate(leafDER)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ inter, err := ParseCertificate(dsaSelfSignedCNX(t))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ inters := NewCertPool()
+ inters.AddCert(root)
+ inters.AddCert(inter)
+
+ wantErr := "certificate signed by unknown authority"
+ _, err = leaf.Verify(VerifyOptions{Intermediates: inters, Roots: NewCertPool()})
+ if !strings.Contains(err.Error(), wantErr) {
+ t.Errorf("got %v, want %q", err, wantErr)
+ }
+}
+
+// dsaSelfSignedCNX produces DER-encoded
+// certificate with the properties:
+//
+// Subject=Issuer=CN=X
+// DSA SPKI
+// Matching inner/outer signature OIDs
+// Dummy ECDSA signature
+func dsaSelfSignedCNX(t *testing.T) []byte {
+ t.Helper()
+ var params dsa.Parameters
+ if err := dsa.GenerateParameters(¶ms, rand.Reader, dsa.L1024N160); err != nil {
+ t.Fatal(err)
+ }
+
+ var dsaPriv dsa.PrivateKey
+ dsaPriv.Parameters = params
+ if err := dsa.GenerateKey(&dsaPriv, rand.Reader); err != nil {
+ t.Fatal(err)
+ }
+ dsaPub := &dsaPriv.PublicKey
+
+ type dsaParams struct{ P, Q, G *big.Int }
+ paramDER, err := asn1.Marshal(dsaParams{dsaPub.P, dsaPub.Q, dsaPub.G})
+ if err != nil {
+ t.Fatal(err)
+ }
+ yDER, err := asn1.Marshal(dsaPub.Y)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ spki := publicKeyInfo{
+ Algorithm: pkix.AlgorithmIdentifier{
+ Algorithm: oidPublicKeyDSA,
+ Parameters: asn1.RawValue{FullBytes: paramDER},
+ },
+ PublicKey: asn1.BitString{Bytes: yDER, BitLength: 8 * len(yDER)},
+ }
+
+ rdn := pkix.Name{CommonName: "X"}.ToRDNSequence()
+ b, err := asn1.Marshal(rdn)
+ if err != nil {
+ t.Fatal(err)
+ }
+ rawName := asn1.RawValue{FullBytes: b}
+
+ algoIdent := pkix.AlgorithmIdentifier{Algorithm: oidSignatureDSAWithSHA256}
+ tbs := tbsCertificate{
+ Version: 0,
+ SerialNumber: big.NewInt(1002),
+ SignatureAlgorithm: algoIdent,
+ Issuer: rawName,
+ Validity: validity{NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(24 * time.Hour)},
+ Subject: rawName,
+ PublicKey: spki,
+ }
+ c := certificate{
+ TBSCertificate: tbs,
+ SignatureAlgorithm: algoIdent,
+ SignatureValue: asn1.BitString{Bytes: []byte{0}, BitLength: 8},
+ }
+ dsaDER, err := asn1.Marshal(c)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return dsaDER
+}
From 3fc4c79fdbb17b9b29ea9f8c29dd780df075d4c4 Mon Sep 17 00:00:00 2001
From: Neal Patel
Date: Mon, 15 Sep 2025 16:31:22 -0400
Subject: [PATCH 038/421] crypto/x509: improve domain name verification
Don't use domainToReverseLabels to check if domain names are valid,
since it is not particularly performant, and can contribute to DoS
vectors. Instead just iterate over the name and enforce the properties
we care about.
This also enforces that DNS names, both in SANs and name constraints,
are valid. We previously allowed invalid SANs, because some
intermediates had these weird names (see #23995), but there are
currently no trusted intermediates that have this property, and since we
target the web PKI, supporting this particular case is not a high
priority.
Thank you to Jakub Ciolek for reporting this issue.
Fixes CVE-2025-58187
Fixes #75681
Change-Id: I6ebce847dcbe5fc63ef2f9a74f53f11c4c56d3d1
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2820
Reviewed-by: Damien Neil
Reviewed-by: Roland Shoemaker
Reviewed-on: https://go-review.googlesource.com/c/go/+/709854
Auto-Submit: Michael Pratt
Reviewed-by: Carlos Amedee
LUCI-TryBot-Result: Go LUCI
---
src/crypto/x509/name_constraints_test.go | 75 ++---------------------
src/crypto/x509/parser.go | 77 ++++++++++++++----------
src/crypto/x509/parser_test.go | 43 +++++++++++++
src/crypto/x509/verify.go | 1 +
4 files changed, 96 insertions(+), 100 deletions(-)
diff --git a/src/crypto/x509/name_constraints_test.go b/src/crypto/x509/name_constraints_test.go
index a5851845164..831fcbc8d2e 100644
--- a/src/crypto/x509/name_constraints_test.go
+++ b/src/crypto/x509/name_constraints_test.go
@@ -1456,63 +1456,7 @@ var nameConstraintsTests = []nameConstraintsTest{
expectedError: "incompatible key usage",
},
- // An invalid DNS SAN should be detected only at validation time so
- // that we can process CA certificates in the wild that have invalid SANs.
- // See https://github.com/golang/go/issues/23995
-
- // #77: an invalid DNS or mail SAN will not be detected if name constraint
- // checking is not triggered.
- {
- roots: make([]constraintsSpec, 1),
- intermediates: [][]constraintsSpec{
- {
- {},
- },
- },
- leaf: leafSpec{
- sans: []string{"dns:this is invalid", "email:this @ is invalid"},
- },
- },
-
- // #78: an invalid DNS SAN will be detected if any name constraint checking
- // is triggered.
- {
- roots: []constraintsSpec{
- {
- bad: []string{"uri:"},
- },
- },
- intermediates: [][]constraintsSpec{
- {
- {},
- },
- },
- leaf: leafSpec{
- sans: []string{"dns:this is invalid"},
- },
- expectedError: "cannot parse dnsName",
- },
-
- // #79: an invalid email SAN will be detected if any name constraint
- // checking is triggered.
- {
- roots: []constraintsSpec{
- {
- bad: []string{"uri:"},
- },
- },
- intermediates: [][]constraintsSpec{
- {
- {},
- },
- },
- leaf: leafSpec{
- sans: []string{"email:this @ is invalid"},
- },
- expectedError: "cannot parse rfc822Name",
- },
-
- // #80: if several EKUs are requested, satisfying any of them is sufficient.
+ // #77: if several EKUs are requested, satisfying any of them is sufficient.
{
roots: make([]constraintsSpec, 1),
intermediates: [][]constraintsSpec{
@@ -1527,7 +1471,7 @@ var nameConstraintsTests = []nameConstraintsTest{
requestedEKUs: []ExtKeyUsage{ExtKeyUsageClientAuth, ExtKeyUsageEmailProtection},
},
- // #81: EKUs that are not asserted in VerifyOpts are not required to be
+ // #78: EKUs that are not asserted in VerifyOpts are not required to be
// nested.
{
roots: make([]constraintsSpec, 1),
@@ -1546,7 +1490,7 @@ var nameConstraintsTests = []nameConstraintsTest{
},
},
- // #82: a certificate without SANs and CN is accepted in a constrained chain.
+ // #79: a certificate without SANs and CN is accepted in a constrained chain.
{
roots: []constraintsSpec{
{
@@ -1563,7 +1507,7 @@ var nameConstraintsTests = []nameConstraintsTest{
},
},
- // #83: a certificate without SANs and with a CN that does not parse as a
+ // #80: a certificate without SANs and with a CN that does not parse as a
// hostname is accepted in a constrained chain.
{
roots: []constraintsSpec{
@@ -1582,7 +1526,7 @@ var nameConstraintsTests = []nameConstraintsTest{
},
},
- // #84: a certificate with SANs and CN is accepted in a constrained chain.
+ // #81: a certificate with SANs and CN is accepted in a constrained chain.
{
roots: []constraintsSpec{
{
@@ -1600,14 +1544,7 @@ var nameConstraintsTests = []nameConstraintsTest{
},
},
- // #85: .example.com is an invalid DNS name, it should not match the
- // constraint example.com.
- {
- roots: []constraintsSpec{{ok: []string{"dns:example.com"}}},
- leaf: leafSpec{sans: []string{"dns:.example.com"}},
- expectedError: "cannot parse dnsName \".example.com\"",
- },
- // #86: URIs with IPv6 addresses with zones and ports are rejected
+ // #82: URIs with IPv6 addresses with zones and ports are rejected
{
roots: []constraintsSpec{
{
diff --git a/src/crypto/x509/parser.go b/src/crypto/x509/parser.go
index 4abcc1b7b59..9d6bfd6e95f 100644
--- a/src/crypto/x509/parser.go
+++ b/src/crypto/x509/parser.go
@@ -413,10 +413,14 @@ func parseSANExtension(der cryptobyte.String) (dnsNames, emailAddresses []string
if err := isIA5String(email); err != nil {
return errors.New("x509: SAN rfc822Name is malformed")
}
+ parsed, ok := parseRFC2821Mailbox(email)
+ if !ok || (ok && !domainNameValid(parsed.domain, false)) {
+ return errors.New("x509: SAN rfc822Name is malformed")
+ }
emailAddresses = append(emailAddresses, email)
case nameTypeDNS:
name := string(data)
- if err := isIA5String(name); err != nil {
+ if err := isIA5String(name); err != nil || (err == nil && !domainNameValid(name, false)) {
return errors.New("x509: SAN dNSName is malformed")
}
dnsNames = append(dnsNames, string(name))
@@ -426,14 +430,9 @@ func parseSANExtension(der cryptobyte.String) (dnsNames, emailAddresses []string
return errors.New("x509: SAN uniformResourceIdentifier is malformed")
}
uri, err := url.Parse(uriStr)
- if err != nil {
+ if err != nil || (err == nil && uri.Host != "" && !domainNameValid(uri.Host, false)) {
return fmt.Errorf("x509: cannot parse URI %q: %s", uriStr, err)
}
- if len(uri.Host) > 0 {
- if _, ok := domainToReverseLabels(uri.Host); !ok {
- return fmt.Errorf("x509: cannot parse URI %q: invalid domain", uriStr)
- }
- }
uris = append(uris, uri)
case nameTypeIP:
switch len(data) {
@@ -598,15 +597,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
}
- trimmedDomain := domain
- if len(trimmedDomain) > 0 && trimmedDomain[0] == '.' {
- // constraints can have a leading
- // period to exclude the domain
- // itself, but that's not valid in a
- // normal domain name.
- trimmedDomain = trimmedDomain[1:]
- }
- if _, ok := domainToReverseLabels(trimmedDomain); !ok {
+ if !domainNameValid(domain, true) {
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse dnsName constraint %q", domain)
}
dnsNames = append(dnsNames, domain)
@@ -647,12 +638,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
}
} else {
- // Otherwise it's a domain name.
- domain := constraint
- if len(domain) > 0 && domain[0] == '.' {
- domain = domain[1:]
- }
- if _, ok := domainToReverseLabels(domain); !ok {
+ if !domainNameValid(constraint, true) {
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
}
}
@@ -668,15 +654,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q: cannot be IP address", domain)
}
- trimmedDomain := domain
- if len(trimmedDomain) > 0 && trimmedDomain[0] == '.' {
- // constraints can have a leading
- // period to exclude the domain itself,
- // but that's not valid in a normal
- // domain name.
- trimmedDomain = trimmedDomain[1:]
- }
- if _, ok := domainToReverseLabels(trimmedDomain); !ok {
+ if !domainNameValid(domain, true) {
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q", domain)
}
uriDomains = append(uriDomains, domain)
@@ -1317,3 +1295,40 @@ func ParseRevocationList(der []byte) (*RevocationList, error) {
return rl, nil
}
+
+// domainNameValid does minimal domain name validity checking. In particular it
+// enforces the following properties:
+// - names cannot have the trailing period
+// - names can only have a leading period if constraint is true
+// - names must be <= 253 characters
+// - names cannot have empty labels
+// - names cannot labels that are longer than 63 characters
+//
+// Note that this does not enforce the LDH requirements for domain names.
+func domainNameValid(s string, constraint bool) bool {
+ if len(s) == 0 && constraint {
+ return true
+ }
+ if len(s) == 0 || (!constraint && s[0] == '.') || s[len(s)-1] == '.' || len(s) > 253 {
+ return false
+ }
+ lastDot := -1
+ if constraint && s[0] == '.' {
+ s = s[1:]
+ }
+
+ for i := 0; i <= len(s); i++ {
+ if i == len(s) || s[i] == '.' {
+ labelLen := i
+ if lastDot >= 0 {
+ labelLen -= lastDot + 1
+ }
+ if labelLen == 0 || labelLen > 63 {
+ return false
+ }
+ lastDot = i
+ }
+ }
+
+ return true
+}
diff --git a/src/crypto/x509/parser_test.go b/src/crypto/x509/parser_test.go
index 3b9d9aed826..1b553e362e4 100644
--- a/src/crypto/x509/parser_test.go
+++ b/src/crypto/x509/parser_test.go
@@ -8,6 +8,7 @@ import (
"encoding/asn1"
"encoding/pem"
"os"
+ "strings"
"testing"
cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
@@ -251,3 +252,45 @@ d5l1tRhScKu2NBgm74nYmJxJYgvuTA38wGhRrGU=
}
}
}
+
+func TestDomainNameValid(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ dnsName string
+ constraint bool
+ valid bool
+ }{
+ {"empty name, name", "", false, false},
+ {"empty name, constraint", "", true, true},
+ {"empty label, name", "a..a", false, false},
+ {"empty label, constraint", "a..a", true, false},
+ {"period, name", ".", false, false},
+ {"period, constraint", ".", true, false}, // TODO(roland): not entirely clear if this is a valid constraint (require at least one label?)
+ {"valid, name", "a.b.c", false, true},
+ {"valid, constraint", "a.b.c", true, true},
+ {"leading period, name", ".a.b.c", false, false},
+ {"leading period, constraint", ".a.b.c", true, true},
+ {"trailing period, name", "a.", false, false},
+ {"trailing period, constraint", "a.", true, false},
+ {"bare label, name", "a", false, true},
+ {"bare label, constraint", "a", true, true},
+ {"254 char label, name", strings.Repeat("a.a", 84) + "aaa", false, false},
+ {"254 char label, constraint", strings.Repeat("a.a", 84) + "aaa", true, false},
+ {"253 char label, name", strings.Repeat("a.a", 84) + "aa", false, false},
+ {"253 char label, constraint", strings.Repeat("a.a", 84) + "aa", true, false},
+ {"64 char single label, name", strings.Repeat("a", 64), false, false},
+ {"64 char single label, constraint", strings.Repeat("a", 64), true, false},
+ {"63 char single label, name", strings.Repeat("a", 63), false, true},
+ {"63 char single label, constraint", strings.Repeat("a", 63), true, true},
+ {"64 char label, name", "a." + strings.Repeat("a", 64), false, false},
+ {"64 char label, constraint", "a." + strings.Repeat("a", 64), true, false},
+ {"63 char label, name", "a." + strings.Repeat("a", 63), false, true},
+ {"63 char label, constraint", "a." + strings.Repeat("a", 63), true, true},
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ if tc.valid != domainNameValid(tc.dnsName, tc.constraint) {
+ t.Errorf("domainNameValid(%q, %t) = %v; want %v", tc.dnsName, tc.constraint, !tc.valid, tc.valid)
+ }
+ })
+ }
+}
diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go
index 755c1db96c1..058153fbe73 100644
--- a/src/crypto/x509/verify.go
+++ b/src/crypto/x509/verify.go
@@ -391,6 +391,7 @@ func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) {
// domainToReverseLabels converts a textual domain name like foo.example.com to
// the list of labels in reverse order, e.g. ["com", "example", "foo"].
func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) {
+ reverseLabels = make([]string, 0, strings.Count(domain, ".")+1)
for len(domain) > 0 {
if i := strings.LastIndexByte(domain, '.'); i == -1 {
reverseLabels = append(reverseLabels, domain)
From 9b9d02c5a015910ce57024788de2ff254c6cfca6 Mon Sep 17 00:00:00 2001
From: Nicholas Husin
Date: Tue, 30 Sep 2025 14:02:38 -0400
Subject: [PATCH 039/421] net/http: add httpcookiemaxnum GODEBUG option to
limit number of cookies parsed
When handling HTTP headers, net/http does not currently limit the number
of cookies that can be parsed. The only limitation that exists is for
the size of the entire HTTP header, which is controlled by
MaxHeaderBytes (defaults to 1 MB).
Unfortunately, this allows a malicious actor to send HTTP headers which
contain a massive amount of small cookies, such that as much cookies as
possible can be fitted within the MaxHeaderBytes limitation. Internally,
this causes us to allocate a massive number of Cookie struct.
For example, a 1 MB HTTP header with cookies that repeats "a=;" will
cause an allocation of ~66 MB in the heap. This can serve as a way for
malicious actors to induce memory exhaustion.
To fix this, we will now limit the number of cookies we are willing to
parse to 3000 by default. This behavior can be changed by setting a new
GODEBUG option: GODEBUG=httpcookiemaxnum. httpcookiemaxnum can be set to
allow a higher or lower cookie limit. Setting it to 0 will also allow an
infinite number of cookies to be parsed.
Thanks to jub0bs for reporting this issue.
For #75672
Fixes CVE-2025-58186
Change-Id: Ied58b3bc8acf5d11c880f881f36ecbf1d5d52622
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2720
Reviewed-by: Roland Shoemaker
Reviewed-by: Damien Neil
Reviewed-on: https://go-review.googlesource.com/c/go/+/709855
Reviewed-by: Carlos Amedee
LUCI-TryBot-Result: Go LUCI
Auto-Submit: Michael Pratt
---
doc/godebug.md | 10 ++
src/internal/godebugs/table.go | 1 +
src/net/http/cookie.go | 59 +++++++++-
src/net/http/cookie_test.go | 206 ++++++++++++++++++++++-----------
src/runtime/metrics/doc.go | 5 +
5 files changed, 206 insertions(+), 75 deletions(-)
diff --git a/doc/godebug.md b/doc/godebug.md
index aaa0f9dd55e..c12ce5311d9 100644
--- a/doc/godebug.md
+++ b/doc/godebug.md
@@ -153,6 +153,16 @@ for example,
see the [runtime documentation](/pkg/runtime#hdr-Environment_Variables)
and the [go command documentation](/cmd/go#hdr-Build_and_test_caching).
+### Go 1.26
+
+Go 1.26 added a new `httpcookiemaxnum` setting that controls the maximum number
+of cookies that net/http will accept when parsing HTTP headers. If the number of
+cookie in a header exceeds the number set in `httpcookiemaxnum`, cookie parsing
+will fail early. The default value is `httpcookiemaxnum=3000`. Setting
+`httpcookiemaxnum=0` will allow the cookie parsing to accept an indefinite
+number of cookies. To avoid denial of service attacks, this setting and default
+was backported to Go 1.25.2 and Go 1.24.8.
+
### Go 1.25
Go 1.25 added a new `decoratemappings` setting that controls whether the Go
diff --git a/src/internal/godebugs/table.go b/src/internal/godebugs/table.go
index 2d008825459..852305e8553 100644
--- a/src/internal/godebugs/table.go
+++ b/src/internal/godebugs/table.go
@@ -42,6 +42,7 @@ var All = []Info{
{Name: "http2client", Package: "net/http"},
{Name: "http2debug", Package: "net/http", Opaque: true},
{Name: "http2server", Package: "net/http"},
+ {Name: "httpcookiemaxnum", Package: "net/http", Changed: 24, Old: "0"},
{Name: "httplaxcontentlength", Package: "net/http", Changed: 22, Old: "1"},
{Name: "httpmuxgo121", Package: "net/http", Changed: 22, Old: "1"},
{Name: "httpservecontentkeepheaders", Package: "net/http", Changed: 23, Old: "1"},
diff --git a/src/net/http/cookie.go b/src/net/http/cookie.go
index efe6cc3e77e..f74bc1043c5 100644
--- a/src/net/http/cookie.go
+++ b/src/net/http/cookie.go
@@ -7,6 +7,7 @@ package http
import (
"errors"
"fmt"
+ "internal/godebug"
"log"
"net"
"net/http/internal/ascii"
@@ -16,6 +17,8 @@ import (
"time"
)
+var httpcookiemaxnum = godebug.New("httpcookiemaxnum")
+
// A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
// HTTP response or the Cookie header of an HTTP request.
//
@@ -58,16 +61,37 @@ const (
)
var (
- errBlankCookie = errors.New("http: blank cookie")
- errEqualNotFoundInCookie = errors.New("http: '=' not found in cookie")
- errInvalidCookieName = errors.New("http: invalid cookie name")
- errInvalidCookieValue = errors.New("http: invalid cookie value")
+ errBlankCookie = errors.New("http: blank cookie")
+ errEqualNotFoundInCookie = errors.New("http: '=' not found in cookie")
+ errInvalidCookieName = errors.New("http: invalid cookie name")
+ errInvalidCookieValue = errors.New("http: invalid cookie value")
+ errCookieNumLimitExceeded = errors.New("http: number of cookies exceeded limit")
)
+const defaultCookieMaxNum = 3000
+
+func cookieNumWithinMax(cookieNum int) bool {
+ withinDefaultMax := cookieNum <= defaultCookieMaxNum
+ if httpcookiemaxnum.Value() == "" {
+ return withinDefaultMax
+ }
+ if customMax, err := strconv.Atoi(httpcookiemaxnum.Value()); err == nil {
+ withinCustomMax := customMax == 0 || cookieNum <= customMax
+ if withinDefaultMax != withinCustomMax {
+ httpcookiemaxnum.IncNonDefault()
+ }
+ return withinCustomMax
+ }
+ return withinDefaultMax
+}
+
// ParseCookie parses a Cookie header value and returns all the cookies
// which were set in it. Since the same cookie name can appear multiple times
// the returned Values can contain more than one value for a given key.
func ParseCookie(line string) ([]*Cookie, error) {
+ if !cookieNumWithinMax(strings.Count(line, ";") + 1) {
+ return nil, errCookieNumLimitExceeded
+ }
parts := strings.Split(textproto.TrimString(line), ";")
if len(parts) == 1 && parts[0] == "" {
return nil, errBlankCookie
@@ -197,11 +221,21 @@ func ParseSetCookie(line string) (*Cookie, error) {
// readSetCookies parses all "Set-Cookie" values from
// the header h and returns the successfully parsed Cookies.
+//
+// If the amount of cookies exceeds CookieNumLimit, and httpcookielimitnum
+// GODEBUG option is not explicitly turned off, this function will silently
+// fail and return an empty slice.
func readSetCookies(h Header) []*Cookie {
cookieCount := len(h["Set-Cookie"])
if cookieCount == 0 {
return []*Cookie{}
}
+ // Cookie limit was unfortunately introduced at a later point in time.
+ // As such, we can only fail by returning an empty slice rather than
+ // explicit error.
+ if !cookieNumWithinMax(cookieCount) {
+ return []*Cookie{}
+ }
cookies := make([]*Cookie, 0, cookieCount)
for _, line := range h["Set-Cookie"] {
if cookie, err := ParseSetCookie(line); err == nil {
@@ -329,13 +363,28 @@ func (c *Cookie) Valid() error {
// readCookies parses all "Cookie" values from the header h and
// returns the successfully parsed Cookies.
//
-// if filter isn't empty, only cookies of that name are returned.
+// If filter isn't empty, only cookies of that name are returned.
+//
+// If the amount of cookies exceeds CookieNumLimit, and httpcookielimitnum
+// GODEBUG option is not explicitly turned off, this function will silently
+// fail and return an empty slice.
func readCookies(h Header, filter string) []*Cookie {
lines := h["Cookie"]
if len(lines) == 0 {
return []*Cookie{}
}
+ // Cookie limit was unfortunately introduced at a later point in time.
+ // As such, we can only fail by returning an empty slice rather than
+ // explicit error.
+ cookieCount := 0
+ for _, line := range lines {
+ cookieCount += strings.Count(line, ";") + 1
+ }
+ if !cookieNumWithinMax(cookieCount) {
+ return []*Cookie{}
+ }
+
cookies := make([]*Cookie, 0, len(lines)+strings.Count(lines[0], ";"))
for _, line := range lines {
line = textproto.TrimString(line)
diff --git a/src/net/http/cookie_test.go b/src/net/http/cookie_test.go
index 8db4957b2cc..f452b4ec768 100644
--- a/src/net/http/cookie_test.go
+++ b/src/net/http/cookie_test.go
@@ -11,6 +11,7 @@ import (
"log"
"os"
"reflect"
+ "slices"
"strings"
"testing"
"time"
@@ -255,16 +256,17 @@ func TestAddCookie(t *testing.T) {
}
var readSetCookiesTests = []struct {
- Header Header
- Cookies []*Cookie
+ header Header
+ cookies []*Cookie
+ godebug string
}{
{
- Header{"Set-Cookie": {"Cookie-1=v$1"}},
- []*Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}},
+ header: Header{"Set-Cookie": {"Cookie-1=v$1"}},
+ cookies: []*Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}},
},
{
- Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}},
- []*Cookie{{
+ header: Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}},
+ cookies: []*Cookie{{
Name: "NID",
Value: "99=YsDT5i3E-CXax-",
Path: "/",
@@ -276,8 +278,8 @@ var readSetCookiesTests = []struct {
}},
},
{
- Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}},
- []*Cookie{{
+ header: Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}},
+ cookies: []*Cookie{{
Name: ".ASPXAUTH",
Value: "7E3AA",
Path: "/",
@@ -288,8 +290,8 @@ var readSetCookiesTests = []struct {
}},
},
{
- Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}},
- []*Cookie{{
+ header: Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}},
+ cookies: []*Cookie{{
Name: "ASP.NET_SessionId",
Value: "foo",
Path: "/",
@@ -298,8 +300,8 @@ var readSetCookiesTests = []struct {
}},
},
{
- Header{"Set-Cookie": {"samesitedefault=foo; SameSite"}},
- []*Cookie{{
+ header: Header{"Set-Cookie": {"samesitedefault=foo; SameSite"}},
+ cookies: []*Cookie{{
Name: "samesitedefault",
Value: "foo",
SameSite: SameSiteDefaultMode,
@@ -307,8 +309,8 @@ var readSetCookiesTests = []struct {
}},
},
{
- Header{"Set-Cookie": {"samesiteinvalidisdefault=foo; SameSite=invalid"}},
- []*Cookie{{
+ header: Header{"Set-Cookie": {"samesiteinvalidisdefault=foo; SameSite=invalid"}},
+ cookies: []*Cookie{{
Name: "samesiteinvalidisdefault",
Value: "foo",
SameSite: SameSiteDefaultMode,
@@ -316,8 +318,8 @@ var readSetCookiesTests = []struct {
}},
},
{
- Header{"Set-Cookie": {"samesitelax=foo; SameSite=Lax"}},
- []*Cookie{{
+ header: Header{"Set-Cookie": {"samesitelax=foo; SameSite=Lax"}},
+ cookies: []*Cookie{{
Name: "samesitelax",
Value: "foo",
SameSite: SameSiteLaxMode,
@@ -325,8 +327,8 @@ var readSetCookiesTests = []struct {
}},
},
{
- Header{"Set-Cookie": {"samesitestrict=foo; SameSite=Strict"}},
- []*Cookie{{
+ header: Header{"Set-Cookie": {"samesitestrict=foo; SameSite=Strict"}},
+ cookies: []*Cookie{{
Name: "samesitestrict",
Value: "foo",
SameSite: SameSiteStrictMode,
@@ -334,8 +336,8 @@ var readSetCookiesTests = []struct {
}},
},
{
- Header{"Set-Cookie": {"samesitenone=foo; SameSite=None"}},
- []*Cookie{{
+ header: Header{"Set-Cookie": {"samesitenone=foo; SameSite=None"}},
+ cookies: []*Cookie{{
Name: "samesitenone",
Value: "foo",
SameSite: SameSiteNoneMode,
@@ -345,47 +347,66 @@ var readSetCookiesTests = []struct {
// Make sure we can properly read back the Set-Cookie headers we create
// for values containing spaces or commas:
{
- Header{"Set-Cookie": {`special-1=a z`}},
- []*Cookie{{Name: "special-1", Value: "a z", Raw: `special-1=a z`}},
+ header: Header{"Set-Cookie": {`special-1=a z`}},
+ cookies: []*Cookie{{Name: "special-1", Value: "a z", Raw: `special-1=a z`}},
},
{
- Header{"Set-Cookie": {`special-2=" z"`}},
- []*Cookie{{Name: "special-2", Value: " z", Quoted: true, Raw: `special-2=" z"`}},
+ header: Header{"Set-Cookie": {`special-2=" z"`}},
+ cookies: []*Cookie{{Name: "special-2", Value: " z", Quoted: true, Raw: `special-2=" z"`}},
},
{
- Header{"Set-Cookie": {`special-3="a "`}},
- []*Cookie{{Name: "special-3", Value: "a ", Quoted: true, Raw: `special-3="a "`}},
+ header: Header{"Set-Cookie": {`special-3="a "`}},
+ cookies: []*Cookie{{Name: "special-3", Value: "a ", Quoted: true, Raw: `special-3="a "`}},
},
{
- Header{"Set-Cookie": {`special-4=" "`}},
- []*Cookie{{Name: "special-4", Value: " ", Quoted: true, Raw: `special-4=" "`}},
+ header: Header{"Set-Cookie": {`special-4=" "`}},
+ cookies: []*Cookie{{Name: "special-4", Value: " ", Quoted: true, Raw: `special-4=" "`}},
},
{
- Header{"Set-Cookie": {`special-5=a,z`}},
- []*Cookie{{Name: "special-5", Value: "a,z", Raw: `special-5=a,z`}},
+ header: Header{"Set-Cookie": {`special-5=a,z`}},
+ cookies: []*Cookie{{Name: "special-5", Value: "a,z", Raw: `special-5=a,z`}},
},
{
- Header{"Set-Cookie": {`special-6=",z"`}},
- []*Cookie{{Name: "special-6", Value: ",z", Quoted: true, Raw: `special-6=",z"`}},
+ header: Header{"Set-Cookie": {`special-6=",z"`}},
+ cookies: []*Cookie{{Name: "special-6", Value: ",z", Quoted: true, Raw: `special-6=",z"`}},
},
{
- Header{"Set-Cookie": {`special-7=a,`}},
- []*Cookie{{Name: "special-7", Value: "a,", Raw: `special-7=a,`}},
+ header: Header{"Set-Cookie": {`special-7=a,`}},
+ cookies: []*Cookie{{Name: "special-7", Value: "a,", Raw: `special-7=a,`}},
},
{
- Header{"Set-Cookie": {`special-8=","`}},
- []*Cookie{{Name: "special-8", Value: ",", Quoted: true, Raw: `special-8=","`}},
+ header: Header{"Set-Cookie": {`special-8=","`}},
+ cookies: []*Cookie{{Name: "special-8", Value: ",", Quoted: true, Raw: `special-8=","`}},
},
// Make sure we can properly read back the Set-Cookie headers
// for names containing spaces:
{
- Header{"Set-Cookie": {`special-9 =","`}},
- []*Cookie{{Name: "special-9", Value: ",", Quoted: true, Raw: `special-9 =","`}},
+ header: Header{"Set-Cookie": {`special-9 =","`}},
+ cookies: []*Cookie{{Name: "special-9", Value: ",", Quoted: true, Raw: `special-9 =","`}},
},
// Quoted values (issue #46443)
{
- Header{"Set-Cookie": {`cookie="quoted"`}},
- []*Cookie{{Name: "cookie", Value: "quoted", Quoted: true, Raw: `cookie="quoted"`}},
+ header: Header{"Set-Cookie": {`cookie="quoted"`}},
+ cookies: []*Cookie{{Name: "cookie", Value: "quoted", Quoted: true, Raw: `cookie="quoted"`}},
+ },
+ {
+ header: Header{"Set-Cookie": slices.Repeat([]string{"a="}, defaultCookieMaxNum+1)},
+ cookies: []*Cookie{},
+ },
+ {
+ header: Header{"Set-Cookie": slices.Repeat([]string{"a="}, 10)},
+ cookies: []*Cookie{},
+ godebug: "httpcookiemaxnum=5",
+ },
+ {
+ header: Header{"Set-Cookie": strings.Split(strings.Repeat(";a=", defaultCookieMaxNum+1)[1:], ";")},
+ cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false, Raw: "a="}}, defaultCookieMaxNum+1),
+ godebug: "httpcookiemaxnum=0",
+ },
+ {
+ header: Header{"Set-Cookie": strings.Split(strings.Repeat(";a=", defaultCookieMaxNum+1)[1:], ";")},
+ cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false, Raw: "a="}}, defaultCookieMaxNum+1),
+ godebug: fmt.Sprintf("httpcookiemaxnum=%v", defaultCookieMaxNum+1),
},
// TODO(bradfitz): users have reported seeing this in the
@@ -405,79 +426,103 @@ func toJSON(v any) string {
func TestReadSetCookies(t *testing.T) {
for i, tt := range readSetCookiesTests {
+ t.Setenv("GODEBUG", tt.godebug)
for n := 0; n < 2; n++ { // to verify readSetCookies doesn't mutate its input
- c := readSetCookies(tt.Header)
- if !reflect.DeepEqual(c, tt.Cookies) {
- t.Errorf("#%d readSetCookies: have\n%s\nwant\n%s\n", i, toJSON(c), toJSON(tt.Cookies))
+ c := readSetCookies(tt.header)
+ if !reflect.DeepEqual(c, tt.cookies) {
+ t.Errorf("#%d readSetCookies: have\n%s\nwant\n%s\n", i, toJSON(c), toJSON(tt.cookies))
}
}
}
}
var readCookiesTests = []struct {
- Header Header
- Filter string
- Cookies []*Cookie
+ header Header
+ filter string
+ cookies []*Cookie
+ godebug string
}{
{
- Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
- "",
- []*Cookie{
+ header: Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
+ filter: "",
+ cookies: []*Cookie{
{Name: "Cookie-1", Value: "v$1"},
{Name: "c2", Value: "v2"},
},
},
{
- Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
- "c2",
- []*Cookie{
+ header: Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
+ filter: "c2",
+ cookies: []*Cookie{
{Name: "c2", Value: "v2"},
},
},
{
- Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
- "",
- []*Cookie{
+ header: Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
+ filter: "",
+ cookies: []*Cookie{
{Name: "Cookie-1", Value: "v$1"},
{Name: "c2", Value: "v2"},
},
},
{
- Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
- "c2",
- []*Cookie{
+ header: Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
+ filter: "c2",
+ cookies: []*Cookie{
{Name: "c2", Value: "v2"},
},
},
{
- Header{"Cookie": {`Cookie-1="v$1"; c2="v2"`}},
- "",
- []*Cookie{
+ header: Header{"Cookie": {`Cookie-1="v$1"; c2="v2"`}},
+ filter: "",
+ cookies: []*Cookie{
{Name: "Cookie-1", Value: "v$1", Quoted: true},
{Name: "c2", Value: "v2", Quoted: true},
},
},
{
- Header{"Cookie": {`Cookie-1="v$1"; c2=v2;`}},
- "",
- []*Cookie{
+ header: Header{"Cookie": {`Cookie-1="v$1"; c2=v2;`}},
+ filter: "",
+ cookies: []*Cookie{
{Name: "Cookie-1", Value: "v$1", Quoted: true},
{Name: "c2", Value: "v2"},
},
},
{
- Header{"Cookie": {``}},
- "",
- []*Cookie{},
+ header: Header{"Cookie": {``}},
+ filter: "",
+ cookies: []*Cookie{},
+ },
+ // GODEBUG=httpcookiemaxnum should work regardless if all cookies are sent
+ // via one "Cookie" field, or multiple fields.
+ {
+ header: Header{"Cookie": {strings.Repeat(";a=", defaultCookieMaxNum+1)[1:]}},
+ cookies: []*Cookie{},
+ },
+ {
+ header: Header{"Cookie": slices.Repeat([]string{"a="}, 10)},
+ cookies: []*Cookie{},
+ godebug: "httpcookiemaxnum=5",
+ },
+ {
+ header: Header{"Cookie": {strings.Repeat(";a=", defaultCookieMaxNum+1)[1:]}},
+ cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
+ godebug: "httpcookiemaxnum=0",
+ },
+ {
+ header: Header{"Cookie": slices.Repeat([]string{"a="}, defaultCookieMaxNum+1)},
+ cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
+ godebug: fmt.Sprintf("httpcookiemaxnum=%v", defaultCookieMaxNum+1),
},
}
func TestReadCookies(t *testing.T) {
for i, tt := range readCookiesTests {
+ t.Setenv("GODEBUG", tt.godebug)
for n := 0; n < 2; n++ { // to verify readCookies doesn't mutate its input
- c := readCookies(tt.Header, tt.Filter)
- if !reflect.DeepEqual(c, tt.Cookies) {
- t.Errorf("#%d readCookies:\nhave: %s\nwant: %s\n", i, toJSON(c), toJSON(tt.Cookies))
+ c := readCookies(tt.header, tt.filter)
+ if !reflect.DeepEqual(c, tt.cookies) {
+ t.Errorf("#%d readCookies:\nhave: %s\nwant: %s\n", i, toJSON(c), toJSON(tt.cookies))
}
}
}
@@ -690,6 +735,7 @@ func TestParseCookie(t *testing.T) {
line string
cookies []*Cookie
err error
+ godebug string
}{
{
line: "Cookie-1=v$1",
@@ -723,8 +769,28 @@ func TestParseCookie(t *testing.T) {
line: "k1=\\",
err: errInvalidCookieValue,
},
+ {
+ line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:],
+ err: errCookieNumLimitExceeded,
+ },
+ {
+ line: strings.Repeat(";a=", 10)[1:],
+ err: errCookieNumLimitExceeded,
+ godebug: "httpcookiemaxnum=5",
+ },
+ {
+ line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:],
+ cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
+ godebug: "httpcookiemaxnum=0",
+ },
+ {
+ line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:],
+ cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
+ godebug: fmt.Sprintf("httpcookiemaxnum=%v", defaultCookieMaxNum+1),
+ },
}
for i, tt := range tests {
+ t.Setenv("GODEBUG", tt.godebug)
gotCookies, gotErr := ParseCookie(tt.line)
if !errors.Is(gotErr, tt.err) {
t.Errorf("#%d ParseCookie got error %v, want error %v", i, gotErr, tt.err)
diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go
index e40ce25ff9d..05646132ce4 100644
--- a/src/runtime/metrics/doc.go
+++ b/src/runtime/metrics/doc.go
@@ -309,6 +309,11 @@ Below is the full list of supported metrics, ordered lexicographically.
The number of non-default behaviors executed by the net/http
package due to a non-default GODEBUG=http2server=... setting.
+ /godebug/non-default-behavior/httpcookiemaxnum:events
+ The number of non-default behaviors executed by the net/http
+ package due to a non-default GODEBUG=httpcookiemaxnum=...
+ setting.
+
/godebug/non-default-behavior/httplaxcontentlength:events
The number of non-default behaviors executed by the net/http
package due to a non-default GODEBUG=httplaxcontentlength=...
From 8709a41d5ef7321f486a1857f189c3fee20e8edd Mon Sep 17 00:00:00 2001
From: Nicholas Husin
Date: Wed, 3 Sep 2025 09:30:56 -0400
Subject: [PATCH 040/421] encoding/asn1: prevent memory exhaustion when parsing
using internal/saferio
Within parseSequenceOf, reflect.MakeSlice is being used to pre-allocate
a slice that is needed in order to fully validate the given DER payload.
The size of the slice allocated are also multiple times larger than the
input DER:
- When using asn1.Unmarshal directly, the allocated slice is ~28x
larger.
- When passing in DER using x509.ParseCertificateRequest, the allocated
slice is ~48x larger.
- When passing in DER using ocsp.ParseResponse, the allocated slice is
~137x larger.
As a result, a malicious actor can craft a big empty DER payload,
resulting in an unnecessary large allocation of memories. This can be a
way to cause memory exhaustion.
To prevent this, we now use SliceCapWithSize within internal/saferio to
enforce a memory allocation cap.
Thanks to Jakub Ciolek for reporting this issue.
For #75671
Fixes CVE-2025-58185
Change-Id: Id50e76187eda43f594be75e516b9ca1d2ae6f428
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2700
Reviewed-by: Roland Shoemaker
Reviewed-by: Damien Neil
Reviewed-on: https://go-review.googlesource.com/c/go/+/709856
Reviewed-by: Carlos Amedee
LUCI-TryBot-Result: Go LUCI
Auto-Submit: Michael Pratt
---
src/encoding/asn1/asn1.go | 10 ++++++++-
src/encoding/asn1/asn1_test.go | 38 ++++++++++++++++++++++++++++++++++
src/go/build/deps_test.go | 2 +-
3 files changed, 48 insertions(+), 2 deletions(-)
diff --git a/src/encoding/asn1/asn1.go b/src/encoding/asn1/asn1.go
index 0b64f06d368..f4be515b98e 100644
--- a/src/encoding/asn1/asn1.go
+++ b/src/encoding/asn1/asn1.go
@@ -22,6 +22,7 @@ package asn1
import (
"errors"
"fmt"
+ "internal/saferio"
"math"
"math/big"
"reflect"
@@ -666,10 +667,17 @@ func parseSequenceOf(bytes []byte, sliceType reflect.Type, elemType reflect.Type
offset += t.length
numElements++
}
- ret = reflect.MakeSlice(sliceType, numElements, numElements)
+ elemSize := uint64(elemType.Size())
+ safeCap := saferio.SliceCapWithSize(elemSize, uint64(numElements))
+ if safeCap < 0 {
+ err = SyntaxError{fmt.Sprintf("%s slice too big: %d elements of %d bytes", elemType.Kind(), numElements, elemSize)}
+ return
+ }
+ ret = reflect.MakeSlice(sliceType, 0, safeCap)
params := fieldParameters{}
offset := 0
for i := 0; i < numElements; i++ {
+ ret = reflect.Append(ret, reflect.Zero(elemType))
offset, err = parseField(ret.Index(i), bytes, offset, params)
if err != nil {
return
diff --git a/src/encoding/asn1/asn1_test.go b/src/encoding/asn1/asn1_test.go
index 0597740bd5e..41cc0ba50ec 100644
--- a/src/encoding/asn1/asn1_test.go
+++ b/src/encoding/asn1/asn1_test.go
@@ -7,10 +7,12 @@ package asn1
import (
"bytes"
"encoding/hex"
+ "errors"
"fmt"
"math"
"math/big"
"reflect"
+ "runtime"
"strings"
"testing"
"time"
@@ -1216,3 +1218,39 @@ func TestImplicitTypeRoundtrip(t *testing.T) {
t.Fatalf("Unexpected diff after roundtripping struct\na: %#v\nb: %#v", a, b)
}
}
+
+func TestParsingMemoryConsumption(t *testing.T) {
+ // Craft a syntatically valid, but empty, ~10 MB DER bomb. A successful
+ // unmarshal of this bomb should yield ~280 MB. However, the parsing should
+ // fail due to the empty content; and, in such cases, we want to make sure
+ // that we do not unnecessarily allocate memories.
+ derBomb := make([]byte, 10_000_000)
+ for i := range derBomb {
+ derBomb[i] = 0x30
+ }
+ derBomb = append([]byte{0x30, 0x83, 0x98, 0x96, 0x80}, derBomb...)
+
+ var m runtime.MemStats
+ runtime.GC()
+ runtime.ReadMemStats(&m)
+ memBefore := m.TotalAlloc
+
+ var out []struct {
+ Id []int
+ Critical bool `asn1:"optional"`
+ Value []byte
+ }
+ _, err := Unmarshal(derBomb, &out)
+ if !errors.As(err, &SyntaxError{}) {
+ t.Fatalf("Incorrect error result: want (%v), but got (%v) instead", &SyntaxError{}, err)
+ }
+
+ runtime.ReadMemStats(&m)
+ memDiff := m.TotalAlloc - memBefore
+
+ // Ensure that the memory allocated does not exceed 10<<21 (~20 MB) when
+ // the parsing fails.
+ if memDiff > 10<<21 {
+ t.Errorf("Too much memory allocated while parsing DER: %v MiB", memDiff/1024/1024)
+ }
+}
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index c76b254b23f..d6a18c19fab 100644
--- a/src/go/build/deps_test.go
+++ b/src/go/build/deps_test.go
@@ -564,7 +564,7 @@ var depsRules = `
# CRYPTO-MATH is crypto that exposes math/big APIs - no cgo, net; fmt now ok.
- CRYPTO, FMT, math/big
+ CRYPTO, FMT, math/big, internal/saferio
< crypto/internal/boring/bbig
< crypto/internal/fips140cache
< crypto/rand
From 3ee761739b0cbb074f5a6e8b28b491664ec1414a Mon Sep 17 00:00:00 2001
From: Michael Pratt
Date: Mon, 6 Oct 2025 17:28:37 -0400
Subject: [PATCH 041/421] runtime: free spanQueue on P destroy
Span queues must be empty when destroying a P since we are outside of
the mark phase. But we don't actually free them, so they simply sit
around using memory. More importantly, they are still in
work.spanSPMCs.all, so freeDeadSpanSPMCs must continue traversing past
them until the end of time.
Prior to CL 709575, keeping them in work.spanSPMCs.all allowed programs
with low GOMAXPROCS to continue triggering the bug if they ever had high
GOMAXPROCS in the past.
The spanSPMCs list is singly-linked, so it is not efficient to remove a
random element from the middle. Instead, we simply mark it as dead to
all freeDeadSpanSPMCs to free it when it scans the full list.
For #75771.
Change-Id: I6a6a636cfa22a4bdef0c273d083c91553e923fe5
Reviewed-on: https://go-review.googlesource.com/c/go/+/709656
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Michael Knyszek
---
src/runtime/mgcmark_greenteagc.go | 34 +++++++++++++++++++++++++++++
src/runtime/mgcmark_nogreenteagc.go | 3 +++
src/runtime/proc.go | 2 ++
3 files changed, 39 insertions(+)
diff --git a/src/runtime/mgcmark_greenteagc.go b/src/runtime/mgcmark_greenteagc.go
index 7f8d60349ff..6ebd7ced81b 100644
--- a/src/runtime/mgcmark_greenteagc.go
+++ b/src/runtime/mgcmark_greenteagc.go
@@ -618,6 +618,40 @@ func (q *spanQueue) refill(r *spanSPMC) objptr {
return q.tryGetFast()
}
+// destroy frees all chains in an empty spanQueue.
+//
+// Preconditions:
+// - World is stopped.
+// - GC is outside of the mark phase.
+// - (Therefore) the queue is empty.
+func (q *spanQueue) destroy() {
+ assertWorldStopped()
+ if gcphase != _GCoff {
+ throw("spanQueue.destroy during the mark phase")
+ }
+ if !q.empty() {
+ throw("spanQueue.destroy on non-empty queue")
+ }
+
+ lock(&work.spanSPMCs.lock)
+
+ // Mark each ring as dead. The sweeper will actually free them.
+ //
+ // N.B., we could free directly here, but work.spanSPMCs.all is a
+ // singly-linked list, so we'd need to walk the entire list to find the
+ // previous node. If the list becomes doubly-linked, we can free
+ // directly.
+ for r := (*spanSPMC)(q.chain.tail.Load()); r != nil; r = (*spanSPMC)(r.prev.Load()) {
+ r.dead.Store(true)
+ }
+
+ q.chain.head = nil
+ q.chain.tail.Store(nil)
+ q.putsSinceDrain = 0
+
+ unlock(&work.spanSPMCs.lock)
+}
+
// spanSPMC is a ring buffer of objptrs that represent spans.
// Accessed without a lock.
//
diff --git a/src/runtime/mgcmark_nogreenteagc.go b/src/runtime/mgcmark_nogreenteagc.go
index 883c3451abe..024565ef3e2 100644
--- a/src/runtime/mgcmark_nogreenteagc.go
+++ b/src/runtime/mgcmark_nogreenteagc.go
@@ -63,6 +63,9 @@ func (q *spanQueue) empty() bool {
return true
}
+func (q *spanQueue) destroy() {
+}
+
type spanSPMC struct {
_ sys.NotInHeap
}
diff --git a/src/runtime/proc.go b/src/runtime/proc.go
index d36895b046c..fadd9a59639 100644
--- a/src/runtime/proc.go
+++ b/src/runtime/proc.go
@@ -5824,6 +5824,8 @@ func (pp *p) destroy() {
println("runtime: p id", pp.id, "destroyed during GC phase", phase)
throw("P destroyed while GC is running")
}
+ // We should free the queues though.
+ pp.gcw.spanq.destroy()
clear(pp.sudogbuf[:])
pp.sudogcache = pp.sudogbuf[:0]
From 7dd54e1fd7f3a25fccbb5c6ab7066e2baad23e66 Mon Sep 17 00:00:00 2001
From: Michael Pratt
Date: Mon, 6 Oct 2025 17:55:06 -0400
Subject: [PATCH 042/421] runtime: make work.spanSPMCs.all doubly-linked
Making this a doubly-linked list allows spanQueue.destroy to immediately
remove and free rings rather than simply marking them as dead and
waiting for the sweeper to deal with them.
For #75771.
Change-Id: I6a6a636c0fb6be08ee967cb6d8f0577511a33c13
Reviewed-on: https://go-review.googlesource.com/c/go/+/709657
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Michael Knyszek
---
src/runtime/mgcmark_greenteagc.go | 61 +++++++++++++++++++++----------
1 file changed, 42 insertions(+), 19 deletions(-)
diff --git a/src/runtime/mgcmark_greenteagc.go b/src/runtime/mgcmark_greenteagc.go
index 6ebd7ced81b..7b78611cf7b 100644
--- a/src/runtime/mgcmark_greenteagc.go
+++ b/src/runtime/mgcmark_greenteagc.go
@@ -635,14 +635,22 @@ func (q *spanQueue) destroy() {
lock(&work.spanSPMCs.lock)
- // Mark each ring as dead. The sweeper will actually free them.
- //
- // N.B., we could free directly here, but work.spanSPMCs.all is a
- // singly-linked list, so we'd need to walk the entire list to find the
- // previous node. If the list becomes doubly-linked, we can free
- // directly.
+ // Remove and free each ring.
for r := (*spanSPMC)(q.chain.tail.Load()); r != nil; r = (*spanSPMC)(r.prev.Load()) {
- r.dead.Store(true)
+ prev := r.allprev
+ next := r.allnext
+ if prev != nil {
+ prev.allnext = next
+ }
+ if next != nil {
+ next.allprev = prev
+ }
+ if work.spanSPMCs.all == r {
+ work.spanSPMCs.all = next
+ }
+
+ r.deinit()
+ mheap_.spanSPMCAlloc.free(unsafe.Pointer(r))
}
q.chain.head = nil
@@ -685,6 +693,11 @@ type spanSPMC struct {
// work.spanSPMCs.lock.
allnext *spanSPMC
+ // allprev is the link to the previous spanSPMC on the work.spanSPMCs
+ // list. This is used to find and free dead spanSPMCs. Protected by
+ // work.spanSPMCs.lock.
+ allprev *spanSPMC
+
// dead indicates whether the spanSPMC is no longer in use.
// Protected by the CAS to the prev field of the spanSPMC pointing
// to this spanSPMC. That is, whoever wins that CAS takes ownership
@@ -711,7 +724,11 @@ type spanSPMC struct {
func newSpanSPMC(cap uint32) *spanSPMC {
lock(&work.spanSPMCs.lock)
r := (*spanSPMC)(mheap_.spanSPMCAlloc.alloc())
- r.allnext = work.spanSPMCs.all
+ next := work.spanSPMCs.all
+ r.allnext = next
+ if next != nil {
+ next.allprev = r
+ }
work.spanSPMCs.all = r
unlock(&work.spanSPMCs.lock)
@@ -748,6 +765,8 @@ func (r *spanSPMC) deinit() {
r.head.Store(0)
r.tail.Store(0)
r.cap = 0
+ r.allnext = nil
+ r.allprev = nil
}
// slot returns a pointer to slot i%r.cap.
@@ -780,22 +799,26 @@ func freeDeadSpanSPMCs() {
unlock(&work.spanSPMCs.lock)
return
}
- rp := &work.spanSPMCs.all
- for {
- r := *rp
- if r == nil {
- break
- }
+ r := work.spanSPMCs.all
+ for r != nil {
+ next := r.allnext
if r.dead.Load() {
// It's dead. Deinitialize and free it.
- *rp = r.allnext
+ prev := r.allprev
+ if prev != nil {
+ prev.allnext = next
+ }
+ if next != nil {
+ next.allprev = prev
+ }
+ if work.spanSPMCs.all == r {
+ work.spanSPMCs.all = next
+ }
+
r.deinit()
mheap_.spanSPMCAlloc.free(unsafe.Pointer(r))
- } else {
- // Still alive, likely in some P's chain.
- // Skip it.
- rp = &r.allnext
}
+ r = next
}
unlock(&work.spanSPMCs.lock)
}
From f6f4e8b3ef21299db1ea3a343c3e55e91365a7fd Mon Sep 17 00:00:00 2001
From: Ethan Lee
Date: Fri, 29 Aug 2025 17:35:55 +0000
Subject: [PATCH 043/421] net/url: enforce stricter parsing of bracketed IPv6
hostnames
- Previously, url.Parse did not enforce validation of hostnames within
square brackets.
- RFC 3986 stipulates that only IPv6 hostnames can be embedded within
square brackets in a URL.
- Now, the parsing logic should strictly enforce that only IPv6
hostnames can be resolved when in square brackets. IPv4, IPv4-mapped
addresses and other input will be rejected.
- Update url_test to add test cases that cover the above scenarios.
Thanks to Enze Wang, Jingcheng Yang and Zehui Miao of Tsinghua
University for reporting this issue.
Fixes CVE-2025-47912
Fixes #75678
Change-Id: Iaa41432bf0ee86de95a39a03adae5729e4deb46c
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2680
Reviewed-by: Damien Neil
Reviewed-by: Roland Shoemaker
Reviewed-on: https://go-review.googlesource.com/c/go/+/709857
TryBot-Bypass: Michael Pratt
Reviewed-by: Carlos Amedee
Auto-Submit: Michael Pratt
---
src/go/build/deps_test.go | 10 ++++++----
src/net/url/url.go | 42 +++++++++++++++++++++++++++++----------
src/net/url/url_test.go | 39 ++++++++++++++++++++++++++++++++++++
3 files changed, 77 insertions(+), 14 deletions(-)
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index d6a18c19fab..8966254d0d9 100644
--- a/src/go/build/deps_test.go
+++ b/src/go/build/deps_test.go
@@ -237,7 +237,6 @@ var depsRules = `
internal/types/errors,
mime/quotedprintable,
net/internal/socktest,
- net/url,
runtime/trace,
text/scanner,
text/tabwriter;
@@ -300,6 +299,12 @@ var depsRules = `
FMT
< text/template/parse;
+ internal/bytealg, internal/itoa, math/bits, slices, strconv, unique
+ < net/netip;
+
+ FMT, net/netip
+ < net/url;
+
net/url, text/template/parse
< text/template
< internal/lazytemplate;
@@ -414,9 +419,6 @@ var depsRules = `
< golang.org/x/net/dns/dnsmessage,
golang.org/x/net/lif;
- internal/bytealg, internal/itoa, math/bits, slices, strconv, unique
- < net/netip;
-
os, net/netip
< internal/routebsd;
diff --git a/src/net/url/url.go b/src/net/url/url.go
index 015c5b27519..292bc6bb121 100644
--- a/src/net/url/url.go
+++ b/src/net/url/url.go
@@ -16,6 +16,7 @@ import (
"errors"
"fmt"
"maps"
+ "net/netip"
"path"
"slices"
"strconv"
@@ -642,40 +643,61 @@ func parseAuthority(authority string) (user *Userinfo, host string, err error) {
// parseHost parses host as an authority without user
// information. That is, as host[:port].
func parseHost(host string) (string, error) {
- if strings.HasPrefix(host, "[") {
+ if openBracketIdx := strings.LastIndex(host, "["); openBracketIdx != -1 {
// Parse an IP-Literal in RFC 3986 and RFC 6874.
// E.g., "[fe80::1]", "[fe80::1%25en0]", "[fe80::1]:80".
- i := strings.LastIndex(host, "]")
- if i < 0 {
+ closeBracketIdx := strings.LastIndex(host, "]")
+ if closeBracketIdx < 0 {
return "", errors.New("missing ']' in host")
}
- colonPort := host[i+1:]
+
+ colonPort := host[closeBracketIdx+1:]
if !validOptionalPort(colonPort) {
return "", fmt.Errorf("invalid port %q after host", colonPort)
}
+ unescapedColonPort, err := unescape(colonPort, encodeHost)
+ if err != nil {
+ return "", err
+ }
+ hostname := host[openBracketIdx+1 : closeBracketIdx]
+ var unescapedHostname string
// RFC 6874 defines that %25 (%-encoded percent) introduces
// the zone identifier, and the zone identifier can use basically
// any %-encoding it likes. That's different from the host, which
// can only %-encode non-ASCII bytes.
// We do impose some restrictions on the zone, to avoid stupidity
// like newlines.
- zone := strings.Index(host[:i], "%25")
- if zone >= 0 {
- host1, err := unescape(host[:zone], encodeHost)
+ zoneIdx := strings.Index(hostname, "%25")
+ if zoneIdx >= 0 {
+ hostPart, err := unescape(hostname[:zoneIdx], encodeHost)
if err != nil {
return "", err
}
- host2, err := unescape(host[zone:i], encodeZone)
+ zonePart, err := unescape(hostname[zoneIdx:], encodeZone)
if err != nil {
return "", err
}
- host3, err := unescape(host[i:], encodeHost)
+ unescapedHostname = hostPart + zonePart
+ } else {
+ var err error
+ unescapedHostname, err = unescape(hostname, encodeHost)
if err != nil {
return "", err
}
- return host1 + host2 + host3, nil
}
+
+ // Per RFC 3986, only a host identified by a valid
+ // IPv6 address can be enclosed by square brackets.
+ // This excludes any IPv4 or IPv4-mapped addresses.
+ addr, err := netip.ParseAddr(unescapedHostname)
+ if err != nil {
+ return "", fmt.Errorf("invalid host: %w", err)
+ }
+ if addr.Is4() || addr.Is4In6() {
+ return "", errors.New("invalid IPv6 host")
+ }
+ return "[" + unescapedHostname + "]" + unescapedColonPort, nil
} else if i := strings.LastIndex(host, ":"); i != -1 {
colonPort := host[i:]
if !validOptionalPort(colonPort) {
diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go
index 16e08b63c6d..32065583f27 100644
--- a/src/net/url/url_test.go
+++ b/src/net/url/url_test.go
@@ -383,6 +383,16 @@ var urltests = []URLTest{
},
"",
},
+ // valid IPv6 host with port and path
+ {
+ "https://[2001:db8::1]:8443/test/path",
+ &URL{
+ Scheme: "https",
+ Host: "[2001:db8::1]:8443",
+ Path: "/test/path",
+ },
+ "",
+ },
// host subcomponent; IPv6 address with zone identifier in RFC 6874
{
"http://[fe80::1%25en0]/", // alphanum zone identifier
@@ -707,6 +717,24 @@ var parseRequestURLTests = []struct {
// RFC 6874.
{"http://[fe80::1%en0]/", false},
{"http://[fe80::1%en0]:8080/", false},
+
+ // Tests exercising RFC 3986 compliance
+ {"https://[1:2:3:4:5:6:7:8]", true}, // full IPv6 address
+ {"https://[2001:db8::a:b:c:d]", true}, // compressed IPv6 address
+ {"https://[fe80::1%25eth0]", true}, // link-local address with zone ID (interface name)
+ {"https://[fe80::abc:def%254]", true}, // link-local address with zone ID (interface index)
+ {"https://[2001:db8::1]/path", true}, // compressed IPv6 address with path
+ {"https://[fe80::1%25eth0]/path?query=1", true}, // link-local with zone, path, and query
+
+ {"https://[::ffff:192.0.2.1]", false},
+ {"https://[:1] ", false},
+ {"https://[1:2:3:4:5:6:7:8:9]", false},
+ {"https://[1::1::1]", false},
+ {"https://[1:2:3:]", false},
+ {"https://[ffff::127.0.0.4000]", false},
+ {"https://[0:0::test.com]:80", false},
+ {"https://[2001:db8::test.com]", false},
+ {"https://[test.com]", false},
}
func TestParseRequestURI(t *testing.T) {
@@ -1643,6 +1671,17 @@ func TestParseErrors(t *testing.T) {
{"cache_object:foo", true},
{"cache_object:foo/bar", true},
{"cache_object/:foo/bar", false},
+
+ {"http://[192.168.0.1]/", true}, // IPv4 in brackets
+ {"http://[192.168.0.1]:8080/", true}, // IPv4 in brackets with port
+ {"http://[::ffff:192.168.0.1]/", true}, // IPv4-mapped IPv6 in brackets
+ {"http://[::ffff:192.168.0.1]:8080/", true}, // IPv4-mapped IPv6 in brackets with port
+ {"http://[::ffff:c0a8:1]/", true}, // IPv4-mapped IPv6 in brackets (hex)
+ {"http://[not-an-ip]/", true}, // invalid IP string in brackets
+ {"http://[fe80::1%foo]/", true}, // invalid zone format in brackets
+ {"http://[fe80::1", true}, // missing closing bracket
+ {"http://fe80::1]/", true}, // missing opening bracket
+ {"http://[test.com]/", true}, // domain name in brackets
}
for _, tt := range tests {
u, err := Parse(tt.in)
From 5ce8cd16f3859ec5ac4106ad8ec15d6236f4501b Mon Sep 17 00:00:00 2001
From: Roland Shoemaker
Date: Tue, 30 Sep 2025 11:16:56 -0700
Subject: [PATCH 044/421] encoding/pem: make Decode complexity linear
Because Decode scanned the input first for the first BEGIN line, and
then the first END line, the complexity of Decode is quadratic. If the
input contained a large number of BEGINs and then a single END right at
the end of the input, we would find the first BEGIN, and then scan the
entire input for the END, and fail to parse the block, so move onto the
next BEGIN, scan the entire input for the END, etc.
Instead, look for the first END in the input, and then the first BEGIN
that precedes the found END. We then process the bytes between the BEGIN
and END, and move onto the bytes after the END for further processing.
This gives us linear complexity.
Fixes CVE-2025-61723
Fixes #75676
Change-Id: I813c4f63e78bca4054226c53e13865c781564ccf
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2921
Reviewed-by: Nicholas Husin
Reviewed-by: Damien Neil
Reviewed-on: https://go-review.googlesource.com/c/go/+/709858
TryBot-Bypass: Michael Pratt
Auto-Submit: Michael Pratt
Reviewed-by: Carlos Amedee
---
src/encoding/pem/pem.go | 67 ++++++++++++++++++++----------------
src/encoding/pem/pem_test.go | 13 +++----
2 files changed, 44 insertions(+), 36 deletions(-)
diff --git a/src/encoding/pem/pem.go b/src/encoding/pem/pem.go
index dcc7416ee21..21887008ca2 100644
--- a/src/encoding/pem/pem.go
+++ b/src/encoding/pem/pem.go
@@ -37,7 +37,7 @@ type Block struct {
// line bytes. The remainder of the byte array (also not including the new line
// bytes) is also returned and this will always be smaller than the original
// argument.
-func getLine(data []byte) (line, rest []byte) {
+func getLine(data []byte) (line, rest []byte, consumed int) {
i := bytes.IndexByte(data, '\n')
var j int
if i < 0 {
@@ -49,7 +49,7 @@ func getLine(data []byte) (line, rest []byte) {
i--
}
}
- return bytes.TrimRight(data[0:i], " \t"), data[j:]
+ return bytes.TrimRight(data[0:i], " \t"), data[j:], j
}
// removeSpacesAndTabs returns a copy of its input with all spaces and tabs
@@ -90,20 +90,32 @@ func Decode(data []byte) (p *Block, rest []byte) {
// pemStart begins with a newline. However, at the very beginning of
// the byte array, we'll accept the start string without it.
rest = data
+
for {
- if bytes.HasPrefix(rest, pemStart[1:]) {
- rest = rest[len(pemStart)-1:]
- } else if _, after, ok := bytes.Cut(rest, pemStart); ok {
- rest = after
- } else {
+ // Find the first END line, and then find the last BEGIN line before
+ // the end line. This lets us skip any repeated BEGIN lines that don't
+ // have a matching END.
+ endIndex := bytes.Index(rest, pemEnd)
+ if endIndex < 0 {
return nil, data
}
+ endTrailerIndex := endIndex + len(pemEnd)
+ beginIndex := bytes.LastIndex(rest[:endIndex], pemStart[1:])
+ if beginIndex < 0 || beginIndex > 0 && rest[beginIndex-1] != '\n' {
+ return nil, data
+ }
+ rest = rest[beginIndex+len(pemStart)-1:]
+ endIndex -= beginIndex + len(pemStart) - 1
+ endTrailerIndex -= beginIndex + len(pemStart) - 1
var typeLine []byte
- typeLine, rest = getLine(rest)
+ var consumed int
+ typeLine, rest, consumed = getLine(rest)
if !bytes.HasSuffix(typeLine, pemEndOfLine) {
continue
}
+ endIndex -= consumed
+ endTrailerIndex -= consumed
typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)]
p = &Block{
@@ -117,7 +129,7 @@ func Decode(data []byte) (p *Block, rest []byte) {
if len(rest) == 0 {
return nil, data
}
- line, next := getLine(rest)
+ line, next, consumed := getLine(rest)
key, val, ok := bytes.Cut(line, colon)
if !ok {
@@ -129,21 +141,13 @@ func Decode(data []byte) (p *Block, rest []byte) {
val = bytes.TrimSpace(val)
p.Headers[string(key)] = string(val)
rest = next
+ endIndex -= consumed
+ endTrailerIndex -= consumed
}
- var endIndex, endTrailerIndex int
-
- // If there were no headers, the END line might occur
- // immediately, without a leading newline.
- if len(p.Headers) == 0 && bytes.HasPrefix(rest, pemEnd[1:]) {
- endIndex = 0
- endTrailerIndex = len(pemEnd) - 1
- } else {
- endIndex = bytes.Index(rest, pemEnd)
- endTrailerIndex = endIndex + len(pemEnd)
- }
-
- if endIndex < 0 {
+ // If there were headers, there must be a newline between the headers
+ // and the END line, so endIndex should be >= 0.
+ if len(p.Headers) > 0 && endIndex < 0 {
continue
}
@@ -163,21 +167,24 @@ func Decode(data []byte) (p *Block, rest []byte) {
}
// The line must end with only whitespace.
- if s, _ := getLine(restOfEndLine); len(s) != 0 {
+ if s, _, _ := getLine(restOfEndLine); len(s) != 0 {
continue
}
- base64Data := removeSpacesAndTabs(rest[:endIndex])
- p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data)))
- n, err := base64.StdEncoding.Decode(p.Bytes, base64Data)
- if err != nil {
- continue
+ p.Bytes = []byte{}
+ if endIndex > 0 {
+ base64Data := removeSpacesAndTabs(rest[:endIndex])
+ p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data)))
+ n, err := base64.StdEncoding.Decode(p.Bytes, base64Data)
+ if err != nil {
+ continue
+ }
+ p.Bytes = p.Bytes[:n]
}
- p.Bytes = p.Bytes[:n]
// the -1 is because we might have only matched pemEnd without the
// leading newline if the PEM block was empty.
- _, rest = getLine(rest[endIndex+len(pemEnd)-1:])
+ _, rest, _ = getLine(rest[endIndex+len(pemEnd)-1:])
return p, rest
}
}
diff --git a/src/encoding/pem/pem_test.go b/src/encoding/pem/pem_test.go
index e252ffd8ed1..2c9b3eabcd1 100644
--- a/src/encoding/pem/pem_test.go
+++ b/src/encoding/pem/pem_test.go
@@ -34,7 +34,7 @@ var getLineTests = []GetLineTest{
func TestGetLine(t *testing.T) {
for i, test := range getLineTests {
- x, y := getLine([]byte(test.in))
+ x, y, _ := getLine([]byte(test.in))
if string(x) != test.out1 || string(y) != test.out2 {
t.Errorf("#%d got:%+v,%+v want:%s,%s", i, x, y, test.out1, test.out2)
}
@@ -46,6 +46,7 @@ func TestDecode(t *testing.T) {
if !reflect.DeepEqual(result, certificate) {
t.Errorf("#0 got:%#v want:%#v", result, certificate)
}
+
result, remainder = Decode(remainder)
if !reflect.DeepEqual(result, privateKey) {
t.Errorf("#1 got:%#v want:%#v", result, privateKey)
@@ -68,7 +69,7 @@ func TestDecode(t *testing.T) {
}
result, remainder = Decode(remainder)
- if result == nil || result.Type != "HEADERS" || len(result.Headers) != 1 {
+ if result == nil || result.Type != "VALID HEADERS" || len(result.Headers) != 1 {
t.Errorf("#5 expected single header block but got :%v", result)
}
@@ -381,15 +382,15 @@ ZWAaUoVtWIQ52aKS0p19G99hhb+IVANC4akkdHV4SP8i7MVNZhfUmg==
# This shouldn't be recognised because of the missing newline after the
headers.
------BEGIN HEADERS-----
+-----BEGIN INVALID HEADERS-----
Header: 1
------END HEADERS-----
+-----END INVALID HEADERS-----
# This should be valid, however.
------BEGIN HEADERS-----
+-----BEGIN VALID HEADERS-----
Header: 1
------END HEADERS-----`)
+-----END VALID HEADERS-----`)
var certificate = &Block{Type: "CERTIFICATE",
Headers: map[string]string{},
From 5ede095649db7783726c28390812bca9ce2c684a Mon Sep 17 00:00:00 2001
From: Damien Neil
Date: Tue, 30 Sep 2025 15:11:16 -0700
Subject: [PATCH 045/421] net/textproto: avoid quadratic complexity in
Reader.ReadResponse
Reader.ReadResponse constructed a response string from repeated
string concatenation, permitting a malicious sender to cause excessive
memory allocation and CPU consumption by sending a response consisting
of many short lines.
Use a strings.Builder to construct the string instead.
Thanks to Jakub Ciolek for reporting this issue.
Fixes CVE-2025-61724
Fixes #75716
Change-Id: I1a98ce85a21b830cb25799f9ac9333a67400d736
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2940
Reviewed-by: Roland Shoemaker
Reviewed-by: Nicholas Husin
Reviewed-on: https://go-review.googlesource.com/c/go/+/709859
TryBot-Bypass: Michael Pratt
Auto-Submit: Michael Pratt
Reviewed-by: Carlos Amedee
---
src/net/textproto/reader.go | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/src/net/textproto/reader.go b/src/net/textproto/reader.go
index 668c06c24c1..6df3a630917 100644
--- a/src/net/textproto/reader.go
+++ b/src/net/textproto/reader.go
@@ -285,8 +285,10 @@ func (r *Reader) ReadCodeLine(expectCode int) (code int, message string, err err
//
// An expectCode <= 0 disables the check of the status code.
func (r *Reader) ReadResponse(expectCode int) (code int, message string, err error) {
- code, continued, message, err := r.readCodeLine(expectCode)
+ code, continued, first, err := r.readCodeLine(expectCode)
multi := continued
+ var messageBuilder strings.Builder
+ messageBuilder.WriteString(first)
for continued {
line, err := r.ReadLine()
if err != nil {
@@ -297,12 +299,15 @@ func (r *Reader) ReadResponse(expectCode int) (code int, message string, err err
var moreMessage string
code2, continued, moreMessage, err = parseCodeLine(line, 0)
if err != nil || code2 != code {
- message += "\n" + strings.TrimRight(line, "\r\n")
+ messageBuilder.WriteByte('\n')
+ messageBuilder.WriteString(strings.TrimRight(line, "\r\n"))
continued = true
continue
}
- message += "\n" + moreMessage
+ messageBuilder.WriteByte('\n')
+ messageBuilder.WriteString(moreMessage)
}
+ message = messageBuilder.String()
if err != nil && multi && message != "" {
// replace one line error message with all lines (full message)
err = &Error{code, message}
From 463165699d874ef0ac7965fc5788fe1693eaae9a Mon Sep 17 00:00:00 2001
From: Damien Neil
Date: Thu, 25 Sep 2025 14:41:53 -0700
Subject: [PATCH 046/421] net/mail: avoid quadratic behavior in mail address
parsing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
RFC 5322 domain-literal parsing built the dtext value one character
at a time with string concatenation, resulting in excessive
resource consumption when parsing very large domain-literal values.
Replace with a subslice.
Benchmark not included in this CL because it's too narrow to be
of general ongoing use, but for:
ParseAddress("alice@[" + strings.Repeat("a", 0x40000) + "]")
goos: darwin
goarch: arm64
pkg: net/mail
cpu: Apple M4 Pro
│ /tmp/bench.0 │ /tmp/bench.1 │
│ sec/op │ sec/op vs base │
ParseAddress-14 1987.732m ± 9% 1.524m ± 5% -99.92% (p=0.000 n=10)
│ /tmp/bench.0 │ /tmp/bench.1 │
│ B/op │ B/op vs base │
ParseAddress-14 33692.767Mi ± 0% 1.282Mi ± 0% -100.00% (p=0.000 n=10)
│ /tmp/bench.0 │ /tmp/bench.1 │
│ allocs/op │ allocs/op vs base │
ParseAddress-14 263711.00 ± 0% 17.00 ± 0% -99.99% (p=0.000 n=10)
Thanks to Philippe Antoine (Catena cyber) for reporting this issue.
Fixes CVE-2025-61725
Fixes #75680
Change-Id: Id971c2d5b59882bb476e22fceb7e01ec08234bb7
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2840
Reviewed-by: Roland Shoemaker
Reviewed-by: Nicholas Husin
Reviewed-on: https://go-review.googlesource.com/c/go/+/709860
Reviewed-by: Carlos Amedee
TryBot-Bypass: Michael Pratt
Auto-Submit: Michael Pratt
---
src/net/mail/message.go | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/net/mail/message.go b/src/net/mail/message.go
index 14f839a0307..1502b359625 100644
--- a/src/net/mail/message.go
+++ b/src/net/mail/message.go
@@ -724,7 +724,8 @@ func (p *addrParser) consumeDomainLiteral() (string, error) {
}
// Parse the dtext
- var dtext string
+ dtext := p.s
+ dtextLen := 0
for {
if p.empty() {
return "", errors.New("mail: unclosed domain-literal")
@@ -741,9 +742,10 @@ func (p *addrParser) consumeDomainLiteral() (string, error) {
return "", fmt.Errorf("mail: bad character in domain-literal: %q", r)
}
- dtext += p.s[:size]
+ dtextLen += size
p.s = p.s[size:]
}
+ dtext = dtext[:dtextLen]
// Skip the trailing ]
if !p.consume(']') {
From f7a68d3804efabd271f0338391858bc1e7e57422 Mon Sep 17 00:00:00 2001
From: Damien Neil
Date: Thu, 11 Sep 2025 13:32:10 -0700
Subject: [PATCH 047/421] archive/tar: set a limit on the size of GNU sparse
file 1.0 regions
Sparse files in tar archives contain only the non-zero components
of the file. There are several different encodings for sparse
files. When reading GNU tar pax 1.0 sparse files, archive/tar did
not set a limit on the size of the sparse region data. A malicious
archive containing a large number of sparse blocks could cause
archive/tar to read an unbounded amount of data from the archive
into memory.
Since a malicious input can be highly compressable, a small
compressed input could cause very large allocations.
Cap the size of the sparse block data to the same limit used
for PAX headers (1 MiB).
Thanks to Harshit Gupta (Mr HAX) (https://www.linkedin.com/in/iam-harshit-gupta/)
for reporting this issue.
Fixes CVE-2025-58183
Fixes #75677
Change-Id: I70b907b584a7b8676df8a149a1db728ae681a770
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2800
Reviewed-by: Roland Shoemaker
Reviewed-by: Nicholas Husin
Reviewed-on: https://go-review.googlesource.com/c/go/+/709861
Auto-Submit: Michael Pratt
TryBot-Bypass: Michael Pratt
Reviewed-by: Carlos Amedee
---
src/archive/tar/common.go | 1 +
src/archive/tar/reader.go | 9 +++++++--
src/archive/tar/reader_test.go | 5 +++++
.../tar/testdata/gnu-sparse-many-zeros.tar.bz2 | Bin 0 -> 1642 bytes
4 files changed, 13 insertions(+), 2 deletions(-)
create mode 100644 src/archive/tar/testdata/gnu-sparse-many-zeros.tar.bz2
diff --git a/src/archive/tar/common.go b/src/archive/tar/common.go
index 7b3945ff153..ad31bbb64aa 100644
--- a/src/archive/tar/common.go
+++ b/src/archive/tar/common.go
@@ -39,6 +39,7 @@ var (
errMissData = errors.New("archive/tar: sparse file references non-existent data")
errUnrefData = errors.New("archive/tar: sparse file contains unreferenced data")
errWriteHole = errors.New("archive/tar: write non-NUL byte in sparse hole")
+ errSparseTooLong = errors.New("archive/tar: sparse map too long")
)
type headerError []string
diff --git a/src/archive/tar/reader.go b/src/archive/tar/reader.go
index 8483fb52a28..16ac2f5b17c 100644
--- a/src/archive/tar/reader.go
+++ b/src/archive/tar/reader.go
@@ -531,12 +531,17 @@ func readGNUSparseMap1x0(r io.Reader) (sparseDatas, error) {
cntNewline int64
buf bytes.Buffer
blk block
+ totalSize int
)
// feedTokens copies data in blocks from r into buf until there are
// at least cnt newlines in buf. It will not read more blocks than needed.
feedTokens := func(n int64) error {
for cntNewline < n {
+ totalSize += len(blk)
+ if totalSize > maxSpecialFileSize {
+ return errSparseTooLong
+ }
if _, err := mustReadFull(r, blk[:]); err != nil {
return err
}
@@ -569,8 +574,8 @@ func readGNUSparseMap1x0(r io.Reader) (sparseDatas, error) {
}
// Parse for all member entries.
- // numEntries is trusted after this since a potential attacker must have
- // committed resources proportional to what this library used.
+ // numEntries is trusted after this since feedTokens limits the number of
+ // tokens based on maxSpecialFileSize.
if err := feedTokens(2 * numEntries); err != nil {
return nil, err
}
diff --git a/src/archive/tar/reader_test.go b/src/archive/tar/reader_test.go
index 99340a30471..fca53dae741 100644
--- a/src/archive/tar/reader_test.go
+++ b/src/archive/tar/reader_test.go
@@ -621,6 +621,11 @@ func TestReader(t *testing.T) {
},
Format: FormatPAX,
}},
+ }, {
+ // Small compressed file that uncompresses to
+ // a file with a very large GNU 1.0 sparse map.
+ file: "testdata/gnu-sparse-many-zeros.tar.bz2",
+ err: errSparseTooLong,
}}
for _, v := range vectors {
diff --git a/src/archive/tar/testdata/gnu-sparse-many-zeros.tar.bz2 b/src/archive/tar/testdata/gnu-sparse-many-zeros.tar.bz2
new file mode 100644
index 0000000000000000000000000000000000000000..751d7fd4b68be1a7439413b4089dbbde33a2900a
GIT binary patch
literal 1642
zcmZ>Y%CIzaj8qGb%wCwXiS2H0{DC_Y9GL$z2|RUTNGrcrz`)SKVZdrPcO01{ps1pvVeGD$*OEWa_73ksV;zBwoQh0BPEH%vtSITeAlMvtlB3E`^y)8{
zw~&+g8{-?Ahs_oW3u(QcHog6FpP~f~~=z-9g
zf_wTZ7^XHZn&-}Zc8R{Fft9C2PEyk*pJkiw?fk^YcK6r>1_p%&1_y>jhFQI$94?0z
za?Cl(nljTjt?TS76W-mu3JeU63=9nnhZs#ILKKBvodhkrxK0|)I$ibrgoSju6wr@N
z42%qnElgP^KzXhfrD74$NvWo@w9QvsFnedxz`)4Dz{J4J!Ez-rpv4zx#WM|Ul~dWe
zujeZ~$i0&Z3?dE&76vu}&J`B}T70wwI?n`zc}}^OvF6@lhub?YF)*+QFmN#NCX1lVw7II6Iq$It1A~AA
zg93v=gRdLVZlD!@quw8l{n5lfn)hi|^v!SyiA^y0F{gnCT=X?DusWMCWpyr3vv&I)
ZCg2!yghOkJkj3svnul7NKE7EI0RUq%CTjoy
literal 0
HcmV?d00001
From f2d0d05d28c3493a8f2b5d4e3c0080e18b9a3bdc Mon Sep 17 00:00:00 2001
From: Ian Alexander
Date: Wed, 20 Aug 2025 19:56:29 -0400
Subject: [PATCH 048/421] cmd/go: refactor usage of `MainModules`
This commit refactors usage of the global variable `MainModules` to
the global LoaderState variable of the same name.
This commit is part of the overall effort to eliminate global
modloader state.
[git-generate]
cd src/cmd/go/internal/modload
rf 'mv State.mainModules State.MainModules'
rf 'ex { MainModules -> LoaderState.MainModules }'
for dir in load modcmd modget test tool workcmd ; do
cd ../${dir}
rf 'ex {
import "cmd/go/internal/modload"
modload.MainModules -> modload.LoaderState.MainModules
}'
done
cd ../modload
rf 'rm MainModules'
Change-Id: I15644c84190717d62ae953747a288ec6495ef168
Reviewed-on: https://go-review.googlesource.com/c/go/+/698060
Reviewed-by: Michael Matloob
Reviewed-by: Michael Matloob
LUCI-TryBot-Result: Go LUCI
---
src/cmd/go/internal/load/godebug.go | 4 +-
src/cmd/go/internal/load/pkg.go | 2 +-
src/cmd/go/internal/load/search.go | 4 +-
src/cmd/go/internal/modcmd/download.go | 6 +-
src/cmd/go/internal/modcmd/vendor.go | 20 +++----
src/cmd/go/internal/modcmd/verify.go | 2 +-
src/cmd/go/internal/modget/get.go | 30 +++++-----
src/cmd/go/internal/modget/query.go | 2 +-
src/cmd/go/internal/modload/build.go | 4 +-
src/cmd/go/internal/modload/buildlist.go | 36 ++++++------
src/cmd/go/internal/modload/edit.go | 6 +-
src/cmd/go/internal/modload/import.go | 30 +++++-----
src/cmd/go/internal/modload/init.go | 72 ++++++++++++------------
src/cmd/go/internal/modload/list.go | 2 +-
src/cmd/go/internal/modload/load.go | 44 +++++++--------
src/cmd/go/internal/modload/modfile.go | 36 ++++++------
src/cmd/go/internal/modload/mvs.go | 4 +-
src/cmd/go/internal/modload/query.go | 28 ++++-----
src/cmd/go/internal/modload/search.go | 20 +++----
src/cmd/go/internal/modload/vendor.go | 4 +-
src/cmd/go/internal/test/test.go | 2 +-
src/cmd/go/internal/tool/tool.go | 4 +-
src/cmd/go/internal/workcmd/sync.go | 4 +-
23 files changed, 182 insertions(+), 184 deletions(-)
diff --git a/src/cmd/go/internal/load/godebug.go b/src/cmd/go/internal/load/godebug.go
index ff184384567..c795d42f117 100644
--- a/src/cmd/go/internal/load/godebug.go
+++ b/src/cmd/go/internal/load/godebug.go
@@ -49,7 +49,7 @@ func defaultGODEBUG(p *Package, directives, testDirectives, xtestDirectives []bu
if p.Name != "main" {
return ""
}
- goVersion := modload.MainModules.GoVersion()
+ goVersion := modload.LoaderState.MainModules.GoVersion()
if modload.LoaderState.RootMode == modload.NoRoot && p.Module != nil {
// This is go install pkg@version or go run pkg@version.
// Use the Go version from the package.
@@ -73,7 +73,7 @@ func defaultGODEBUG(p *Package, directives, testDirectives, xtestDirectives []bu
}
// Add directives from main module go.mod.
- for _, g := range modload.MainModules.Godebugs() {
+ for _, g := range modload.LoaderState.MainModules.Godebugs() {
if m == nil {
m = make(map[string]string)
}
diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go
index 27a1fbfff83..135d7579d65 100644
--- a/src/cmd/go/internal/load/pkg.go
+++ b/src/cmd/go/internal/load/pkg.go
@@ -1552,7 +1552,7 @@ func disallowInternal(ctx context.Context, srcDir string, importer *Package, imp
// directory containing them.
// If the directory is outside the main modules, this will resolve to ".",
// which is not a prefix of any valid module.
- importerPath, _ = modload.MainModules.DirImportPath(ctx, importer.Dir)
+ importerPath, _ = modload.LoaderState.MainModules.DirImportPath(ctx, importer.Dir)
}
parentOfInternal := p.ImportPath[:i]
if str.HasPathPrefix(importerPath, parentOfInternal) {
diff --git a/src/cmd/go/internal/load/search.go b/src/cmd/go/internal/load/search.go
index 941cfb77a2e..51c8cc09324 100644
--- a/src/cmd/go/internal/load/search.go
+++ b/src/cmd/go/internal/load/search.go
@@ -56,11 +56,11 @@ func MatchPackage(pattern, cwd string) func(*Package) bool {
return func(p *Package) bool { return p.Standard && strings.HasPrefix(p.ImportPath, "cmd/") }
case pattern == "tool" && modload.Enabled():
return func(p *Package) bool {
- return modload.MainModules.Tools()[p.ImportPath]
+ return modload.LoaderState.MainModules.Tools()[p.ImportPath]
}
case pattern == "work" && modload.Enabled():
return func(p *Package) bool {
- return p.Module != nil && modload.MainModules.Contains(p.Module.Path)
+ return p.Module != nil && modload.LoaderState.MainModules.Contains(p.Module.Path)
}
default:
diff --git a/src/cmd/go/internal/modcmd/download.go b/src/cmd/go/internal/modcmd/download.go
index 6d12d689f0f..8df11bfa59f 100644
--- a/src/cmd/go/internal/modcmd/download.go
+++ b/src/cmd/go/internal/modcmd/download.go
@@ -120,7 +120,7 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) {
modload.LoadModFile(ctx) // to fill MainModules
if haveExplicitArgs {
- for _, mainModule := range modload.MainModules.Versions() {
+ for _, mainModule := range modload.LoaderState.MainModules.Versions() {
targetAtUpgrade := mainModule.Path + "@upgrade"
targetAtPatch := mainModule.Path + "@patch"
for _, arg := range args {
@@ -136,8 +136,8 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) {
// https://go-review.googlesource.com/c/go/+/359794/comments/ce946a80_6cf53992.
args = []string{"all"}
} else {
- mainModule := modload.MainModules.Versions()[0]
- modFile := modload.MainModules.ModFile(mainModule)
+ mainModule := modload.LoaderState.MainModules.Versions()[0]
+ modFile := modload.LoaderState.MainModules.ModFile(mainModule)
if modFile.Go == nil || gover.Compare(modFile.Go.Version, gover.ExplicitIndirectVersion) < 0 {
if len(modFile.Require) > 0 {
args = []string{"all"}
diff --git a/src/cmd/go/internal/modcmd/vendor.go b/src/cmd/go/internal/modcmd/vendor.go
index dfea571c0e0..df673e885c1 100644
--- a/src/cmd/go/internal/modcmd/vendor.go
+++ b/src/cmd/go/internal/modcmd/vendor.go
@@ -106,7 +106,7 @@ func RunVendor(ctx context.Context, vendorE bool, vendorO string, args []string)
modpkgs := make(map[module.Version][]string)
for _, pkg := range pkgs {
m := modload.PackageModule(pkg)
- if m.Path == "" || modload.MainModules.Contains(m.Path) {
+ if m.Path == "" || modload.LoaderState.MainModules.Contains(m.Path) {
continue
}
modpkgs[m] = append(modpkgs[m], pkg)
@@ -116,13 +116,13 @@ func RunVendor(ctx context.Context, vendorE bool, vendorO string, args []string)
includeAllReplacements := false
includeGoVersions := false
isExplicit := map[module.Version]bool{}
- gv := modload.MainModules.GoVersion()
+ gv := modload.LoaderState.MainModules.GoVersion()
if gover.Compare(gv, "1.14") >= 0 && (modload.FindGoWork(base.Cwd()) != "" || modload.ModFile().Go != nil) {
// If the Go version is at least 1.14, annotate all explicit 'require' and
// 'replace' targets found in the go.mod file so that we can perform a
// stronger consistency check when -mod=vendor is set.
- for _, m := range modload.MainModules.Versions() {
- if modFile := modload.MainModules.ModFile(m); modFile != nil {
+ for _, m := range modload.LoaderState.MainModules.Versions() {
+ if modFile := modload.LoaderState.MainModules.ModFile(m); modFile != nil {
for _, r := range modFile.Require {
isExplicit[r.Mod] = true
}
@@ -156,7 +156,7 @@ func RunVendor(ctx context.Context, vendorE bool, vendorO string, args []string)
w = io.MultiWriter(&buf, os.Stderr)
}
- if modload.MainModules.WorkFile() != nil {
+ if modload.LoaderState.MainModules.WorkFile() != nil {
fmt.Fprintf(w, "## workspace\n")
}
@@ -192,8 +192,8 @@ func RunVendor(ctx context.Context, vendorE bool, vendorO string, args []string)
// Record unused and wildcard replacements at the end of the modules.txt file:
// without access to the complete build list, the consumer of the vendor
// directory can't otherwise determine that those replacements had no effect.
- for _, m := range modload.MainModules.Versions() {
- if workFile := modload.MainModules.WorkFile(); workFile != nil {
+ for _, m := range modload.LoaderState.MainModules.Versions() {
+ if workFile := modload.LoaderState.MainModules.WorkFile(); workFile != nil {
for _, r := range workFile.Replace {
if replacementWritten[r.Old] {
// We already recorded this replacement.
@@ -208,7 +208,7 @@ func RunVendor(ctx context.Context, vendorE bool, vendorO string, args []string)
}
}
}
- if modFile := modload.MainModules.ModFile(m); modFile != nil {
+ if modFile := modload.LoaderState.MainModules.ModFile(m); modFile != nil {
for _, r := range modFile.Replace {
if replacementWritten[r.Old] {
// We already recorded this replacement.
@@ -315,7 +315,7 @@ func vendorPkg(vdir, pkg string) {
}
}
var embedPatterns []string
- if gover.Compare(modload.MainModules.GoVersion(), "1.22") >= 0 {
+ if gover.Compare(modload.LoaderState.MainModules.GoVersion(), "1.22") >= 0 {
embedPatterns = bp.EmbedPatterns
} else {
// Maintain the behavior of https://github.com/golang/go/issues/63473
@@ -431,7 +431,7 @@ func matchPotentialSourceFile(dir string, info fs.DirEntry) bool {
return false
}
if info.Name() == "go.mod" || info.Name() == "go.sum" {
- if gv := modload.MainModules.GoVersion(); gover.Compare(gv, "1.17") >= 0 {
+ if gv := modload.LoaderState.MainModules.GoVersion(); gover.Compare(gv, "1.17") >= 0 {
// As of Go 1.17, we strip go.mod and go.sum files from dependency modules.
// Otherwise, 'go' commands invoked within the vendor subtree may misidentify
// an arbitrary directory within the vendor tree as a module root.
diff --git a/src/cmd/go/internal/modcmd/verify.go b/src/cmd/go/internal/modcmd/verify.go
index 157c920c067..8de444ff06a 100644
--- a/src/cmd/go/internal/modcmd/verify.go
+++ b/src/cmd/go/internal/modcmd/verify.go
@@ -94,7 +94,7 @@ func verifyMod(ctx context.Context, mod module.Version) []error {
// "go" and "toolchain" have no disk footprint; nothing to verify.
return nil
}
- if modload.MainModules.Contains(mod.Path) {
+ if modload.LoaderState.MainModules.Contains(mod.Path) {
return nil
}
var errs []error
diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go
index 167f515be98..d8b1f83bf1d 100644
--- a/src/cmd/go/internal/modget/get.go
+++ b/src/cmd/go/internal/modget/get.go
@@ -426,7 +426,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
if gowork := modload.FindGoWork(base.Cwd()); gowork != "" {
wf, err := modload.ReadWorkFile(gowork)
- if err == nil && modload.UpdateWorkGoVersion(wf, modload.MainModules.GoVersion()) {
+ if err == nil && modload.UpdateWorkGoVersion(wf, modload.LoaderState.MainModules.GoVersion()) {
modload.WriteWorkFile(gowork, wf)
}
}
@@ -722,7 +722,7 @@ func (r *resolver) queryNone(ctx context.Context, q *query) {
if !q.isWildcard() {
q.pathOnce(q.pattern, func() pathSet {
hasModRoot := modload.HasModRoot()
- if hasModRoot && modload.MainModules.Contains(q.pattern) {
+ if hasModRoot && modload.LoaderState.MainModules.Contains(q.pattern) {
v := module.Version{Path: q.pattern}
// The user has explicitly requested to downgrade their own module to
// version "none". This is not an entirely unreasonable request: it
@@ -746,7 +746,7 @@ func (r *resolver) queryNone(ctx context.Context, q *query) {
continue
}
q.pathOnce(curM.Path, func() pathSet {
- if modload.HasModRoot() && curM.Version == "" && modload.MainModules.Contains(curM.Path) {
+ if modload.HasModRoot() && curM.Version == "" && modload.LoaderState.MainModules.Contains(curM.Path) {
return errSet(&modload.QueryMatchesMainModulesError{MainModules: []module.Version{curM}, Pattern: q.pattern, Query: q.version})
}
return pathSet{mod: module.Version{Path: curM.Path, Version: "none"}}
@@ -766,13 +766,13 @@ func (r *resolver) performLocalQueries(ctx context.Context) {
// Absolute paths like C:\foo and relative paths like ../foo... are
// restricted to matching packages in the main module.
- pkgPattern, mainModule := modload.MainModules.DirImportPath(ctx, q.pattern)
+ pkgPattern, mainModule := modload.LoaderState.MainModules.DirImportPath(ctx, q.pattern)
if pkgPattern == "." {
modload.MustHaveModRoot()
- versions := modload.MainModules.Versions()
+ versions := modload.LoaderState.MainModules.Versions()
modRoots := make([]string, 0, len(versions))
for _, m := range versions {
- modRoots = append(modRoots, modload.MainModules.ModRoot(m))
+ modRoots = append(modRoots, modload.LoaderState.MainModules.ModRoot(m))
}
var plural string
if len(modRoots) != 1 {
@@ -792,7 +792,7 @@ func (r *resolver) performLocalQueries(ctx context.Context) {
}
if !q.isWildcard() {
modload.MustHaveModRoot()
- return errSet(fmt.Errorf("%s%s is not a package in module rooted at %s", q.pattern, absDetail, modload.MainModules.ModRoot(mainModule)))
+ return errSet(fmt.Errorf("%s%s is not a package in module rooted at %s", q.pattern, absDetail, modload.LoaderState.MainModules.ModRoot(mainModule)))
}
search.WarnUnmatched([]*search.Match{match})
return pathSet{}
@@ -848,7 +848,7 @@ func (r *resolver) queryWildcard(ctx context.Context, q *query) {
return pathSet{}
}
- if modload.MainModules.Contains(curM.Path) && !versionOkForMainModule(q.version) {
+ if modload.LoaderState.MainModules.Contains(curM.Path) && !versionOkForMainModule(q.version) {
if q.matchesPath(curM.Path) {
return errSet(&modload.QueryMatchesMainModulesError{
MainModules: []module.Version{curM},
@@ -1065,7 +1065,7 @@ func (r *resolver) queryPath(ctx context.Context, q *query) {
// pattern is "tool".
func (r *resolver) performToolQueries(ctx context.Context) {
for _, q := range r.toolQueries {
- for tool := range modload.MainModules.Tools() {
+ for tool := range modload.LoaderState.MainModules.Tools() {
q.pathOnce(tool, func() pathSet {
pkgMods, err := r.queryPackages(ctx, tool, q.version, r.initialSelected)
return pathSet{pkgMods: pkgMods, err: err}
@@ -1082,10 +1082,10 @@ func (r *resolver) performWorkQueries(ctx context.Context) {
// TODO(matloob): Maybe export MainModules.mustGetSingleMainModule and call that.
// There are a few other places outside the modload package where we expect
// a single main module.
- if len(modload.MainModules.Versions()) != 1 {
+ if len(modload.LoaderState.MainModules.Versions()) != 1 {
panic("internal error: number of main modules is not exactly one in resolution phase of go get")
}
- mainModule := modload.MainModules.Versions()[0]
+ mainModule := modload.LoaderState.MainModules.Versions()[0]
// We know what the result is going to be, assuming the main module is not
// empty, (it's the main module itself) but first check to see that there
@@ -1496,7 +1496,7 @@ func (r *resolver) disambiguate(cs pathSet) (filtered pathSet, isPackage bool, m
continue
}
- if modload.MainModules.Contains(m.Path) {
+ if modload.LoaderState.MainModules.Contains(m.Path) {
if m.Version == "" {
return pathSet{}, true, m, true
}
@@ -1612,7 +1612,7 @@ func (r *resolver) checkPackageProblems(ctx context.Context, pkgPatterns []strin
// info, but switch back to single module mode when fetching sums so that we update
// the single module's go.sum file.
var exitWorkspace func()
- if r.workspace != nil && r.workspace.hasModule(modload.MainModules.Versions()[0].Path) {
+ if r.workspace != nil && r.workspace.hasModule(modload.LoaderState.MainModules.Versions()[0].Path) {
var err error
exitWorkspace, err = modload.EnterWorkspace(ctx)
if err != nil {
@@ -1951,7 +1951,7 @@ func (r *resolver) resolve(q *query, m module.Version) {
panic("internal error: resolving a module.Version with an empty path")
}
- if modload.MainModules.Contains(m.Path) && m.Version != "" {
+ if modload.LoaderState.MainModules.Contains(m.Path) && m.Version != "" {
reportError(q, &modload.QueryMatchesMainModulesError{
MainModules: []module.Version{{Path: m.Path}},
Pattern: q.pattern,
@@ -1983,7 +1983,7 @@ func (r *resolver) updateBuildList(ctx context.Context, additions []module.Versi
resolved := make([]module.Version, 0, len(r.resolvedVersion))
for mPath, rv := range r.resolvedVersion {
- if !modload.MainModules.Contains(mPath) {
+ if !modload.LoaderState.MainModules.Contains(mPath) {
resolved = append(resolved, module.Version{Path: mPath, Version: rv.version})
}
}
diff --git a/src/cmd/go/internal/modget/query.go b/src/cmd/go/internal/modget/query.go
index 05872d52ec4..db09947293c 100644
--- a/src/cmd/go/internal/modget/query.go
+++ b/src/cmd/go/internal/modget/query.go
@@ -192,7 +192,7 @@ func (q *query) validate() error {
// request that we remove all module requirements, leaving only the main
// module and standard library. Perhaps we should implement that someday.
return &modload.QueryUpgradesAllError{
- MainModules: modload.MainModules.Versions(),
+ MainModules: modload.LoaderState.MainModules.Versions(),
Query: q.version,
}
}
diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go
index 6e30afd5247..cb168d58a22 100644
--- a/src/cmd/go/internal/modload/build.go
+++ b/src/cmd/go/internal/modload/build.go
@@ -290,7 +290,7 @@ func addDeprecation(ctx context.Context, m *modinfo.ModulePublic) {
// in rs (which may be nil to indicate that m was not loaded from a requirement
// graph).
func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode ListMode, reuse map[module.Version]*modinfo.ModulePublic) *modinfo.ModulePublic {
- if m.Version == "" && MainModules.Contains(m.Path) {
+ if m.Version == "" && LoaderState.MainModules.Contains(m.Path) {
info := &modinfo.ModulePublic{
Path: m.Path,
Version: m.Version,
@@ -301,7 +301,7 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
} else {
panic("internal error: GoVersion not set for main module")
}
- if modRoot := MainModules.ModRoot(m); modRoot != "" {
+ if modRoot := LoaderState.MainModules.ModRoot(m); modRoot != "" {
info.Dir = modRoot
info.GoMod = modFilePath(modRoot)
}
diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go
index 2ba04f707b5..8afea0b205c 100644
--- a/src/cmd/go/internal/modload/buildlist.go
+++ b/src/cmd/go/internal/modload/buildlist.go
@@ -128,7 +128,7 @@ func newRequirements(pruning modPruning, rootModules []module.Version, direct ma
panic("in workspace mode, but pruning is not workspace in newRequirements")
}
for i, m := range rootModules {
- if m.Version == "" && MainModules.Contains(m.Path) {
+ if m.Version == "" && LoaderState.MainModules.Contains(m.Path) {
panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is a main module", i))
}
if m.Path == "" || m.Version == "" {
@@ -174,7 +174,7 @@ func (rs *Requirements) String() string {
// requirements.
func (rs *Requirements) initVendor(vendorList []module.Version) {
rs.graphOnce.Do(func() {
- roots := MainModules.Versions()
+ roots := LoaderState.MainModules.Versions()
if inWorkspaceMode() {
// Use rs.rootModules to pull in the go and toolchain roots
// from the go.work file and preserve the invariant that all
@@ -186,7 +186,7 @@ func (rs *Requirements) initVendor(vendorList []module.Version) {
}
if rs.pruning == pruned {
- mainModule := MainModules.mustGetSingleMainModule()
+ mainModule := LoaderState.MainModules.mustGetSingleMainModule()
// The roots of a single pruned module should already include every module in the
// vendor list, because the vendored modules are the same as those needed
// for graph pruning.
@@ -219,14 +219,14 @@ func (rs *Requirements) initVendor(vendorList []module.Version) {
// dependencies.
vendorMod := module.Version{Path: "vendor/modules.txt", Version: ""}
if inWorkspaceMode() {
- for _, m := range MainModules.Versions() {
- reqs, _ := rootsFromModFile(m, MainModules.ModFile(m), omitToolchainRoot)
+ for _, m := range LoaderState.MainModules.Versions() {
+ reqs, _ := rootsFromModFile(m, LoaderState.MainModules.ModFile(m), omitToolchainRoot)
mg.g.Require(m, append(reqs, vendorMod))
}
mg.g.Require(vendorMod, vendorList)
} else {
- mainModule := MainModules.mustGetSingleMainModule()
+ mainModule := LoaderState.MainModules.mustGetSingleMainModule()
mg.g.Require(mainModule, append(rs.rootModules, vendorMod))
mg.g.Require(vendorMod, vendorList)
}
@@ -249,7 +249,7 @@ func (rs *Requirements) GoVersion() string {
// path, or the zero module.Version and ok=false if the module is not a root
// dependency.
func (rs *Requirements) rootSelected(path string) (version string, ok bool) {
- if MainModules.Contains(path) {
+ if LoaderState.MainModules.Contains(path) {
return "", true
}
if v, ok := rs.maxRootVersion[path]; ok {
@@ -264,7 +264,7 @@ func (rs *Requirements) rootSelected(path string) (version string, ok bool) {
// selection.
func (rs *Requirements) hasRedundantRoot() bool {
for i, m := range rs.rootModules {
- if MainModules.Contains(m.Path) || (i > 0 && m.Path == rs.rootModules[i-1].Path) {
+ if LoaderState.MainModules.Contains(m.Path) || (i > 0 && m.Path == rs.rootModules[i-1].Path) {
return true
}
}
@@ -346,7 +346,7 @@ func readModGraph(ctx context.Context, pruning modPruning, roots []module.Versio
if inWorkspaceMode() {
graphRoots = roots
} else {
- graphRoots = MainModules.Versions()
+ graphRoots = LoaderState.MainModules.Versions()
}
var (
mu sync.Mutex // guards mg.g and hasError during loading
@@ -360,7 +360,7 @@ func readModGraph(ctx context.Context, pruning modPruning, roots []module.Versio
if inWorkspaceMode() {
panic("pruning is not workspace in workspace mode")
}
- mg.g.Require(MainModules.mustGetSingleMainModule(), roots)
+ mg.g.Require(LoaderState.MainModules.mustGetSingleMainModule(), roots)
}
type dedupKey struct {
@@ -540,9 +540,9 @@ func (mg *ModuleGraph) findError() error {
func (mg *ModuleGraph) allRootsSelected() bool {
var roots []module.Version
if inWorkspaceMode() {
- roots = MainModules.Versions()
+ roots = LoaderState.MainModules.Versions()
} else {
- roots, _ = mg.g.RequiredBy(MainModules.mustGetSingleMainModule())
+ roots, _ = mg.g.RequiredBy(LoaderState.MainModules.mustGetSingleMainModule())
}
for _, m := range roots {
if mg.Selected(m.Path) != m.Version {
@@ -776,7 +776,7 @@ func (c Conflict) String() string {
// both retain the same versions of all packages in pkgs and satisfy the
// graph-pruning invariants (if applicable).
func tidyRoots(ctx context.Context, rs *Requirements, pkgs []*loadPkg) (*Requirements, error) {
- mainModule := MainModules.mustGetSingleMainModule()
+ mainModule := LoaderState.MainModules.mustGetSingleMainModule()
if rs.pruning == unpruned {
return tidyUnprunedRoots(ctx, mainModule, rs, pkgs)
}
@@ -1168,7 +1168,7 @@ func updatePrunedRoots(ctx context.Context, direct map[string]bool, rs *Requirem
roots = make([]module.Version, 0, len(rs.rootModules))
rootsUpgraded = false
inRootPaths := make(map[string]bool, len(rs.rootModules)+1)
- for _, mm := range MainModules.Versions() {
+ for _, mm := range LoaderState.MainModules.Versions() {
inRootPaths[mm.Path] = true
}
for _, m := range rs.rootModules {
@@ -1445,7 +1445,7 @@ func updateUnprunedRoots(ctx context.Context, direct map[string]bool, rs *Requir
// This is only for convenience and clarity for end users: in an unpruned module,
// the choice of explicit vs. implicit dependency has no impact on MVS
// selection (for itself or any other module).
- keep := append(mg.BuildList()[MainModules.Len():], add...)
+ keep := append(mg.BuildList()[LoaderState.MainModules.Len():], add...)
for _, m := range keep {
if direct[m.Path] && !inRootPaths[m.Path] {
rootPaths = append(rootPaths, m.Path)
@@ -1454,14 +1454,14 @@ func updateUnprunedRoots(ctx context.Context, direct map[string]bool, rs *Requir
}
var roots []module.Version
- for _, mainModule := range MainModules.Versions() {
+ for _, mainModule := range LoaderState.MainModules.Versions() {
min, err := mvs.Req(mainModule, rootPaths, &mvsReqs{roots: keep})
if err != nil {
return rs, err
}
roots = append(roots, min...)
}
- if MainModules.Len() > 1 {
+ if LoaderState.MainModules.Len() > 1 {
gover.ModSort(roots)
}
if rs.pruning == unpruned && slices.Equal(roots, rs.rootModules) && maps.Equal(direct, rs.direct) {
@@ -1501,5 +1501,5 @@ func convertPruning(ctx context.Context, rs *Requirements, pruning modPruning) (
if err != nil {
return rs, err
}
- return newRequirements(pruned, mg.BuildList()[MainModules.Len():], rs.direct), nil
+ return newRequirements(pruned, mg.BuildList()[LoaderState.MainModules.Len():], rs.direct), nil
}
diff --git a/src/cmd/go/internal/modload/edit.go b/src/cmd/go/internal/modload/edit.go
index b406193dc5a..153c21a90cc 100644
--- a/src/cmd/go/internal/modload/edit.go
+++ b/src/cmd/go/internal/modload/edit.go
@@ -106,7 +106,7 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
// to begin with, so we can't edit those requirements in a coherent way.
return orig, false, err
}
- bl := mg.BuildList()[MainModules.Len():]
+ bl := mg.BuildList()[LoaderState.MainModules.Len():]
selectedRoot = make(map[string]string, len(bl))
for _, m := range bl {
selectedRoot[m.Path] = m.Version
@@ -513,7 +513,7 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
// The modules in mustSelect are always promoted to be explicit.
for _, m := range mustSelect {
- if m.Version != "none" && !MainModules.Contains(m.Path) {
+ if m.Version != "none" && !LoaderState.MainModules.Contains(m.Path) {
rootPaths = append(rootPaths, m.Path)
}
}
@@ -530,7 +530,7 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
}
}
- roots, err = mvs.Req(MainModules.mustGetSingleMainModule(), rootPaths, &mvsReqs{roots: roots})
+ roots, err = mvs.Req(LoaderState.MainModules.mustGetSingleMainModule(), rootPaths, &mvsReqs{roots: roots})
if err != nil {
return nil, false, err
}
diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go
index 171d9d692fb..83e7e037117 100644
--- a/src/cmd/go/internal/modload/import.go
+++ b/src/cmd/go/internal/modload/import.go
@@ -82,8 +82,8 @@ func (e *ImportMissingError) Error() string {
if e.QueryErr != nil {
return fmt.Sprintf("%s: %v", message, e.QueryErr)
}
- if e.ImportingMainModule.Path != "" && e.ImportingMainModule != MainModules.ModContainingCWD() {
- return fmt.Sprintf("%s; to add it:\n\tcd %s\n\tgo get %s", message, MainModules.ModRoot(e.ImportingMainModule), e.Path)
+ if e.ImportingMainModule.Path != "" && e.ImportingMainModule != LoaderState.MainModules.ModContainingCWD() {
+ return fmt.Sprintf("%s; to add it:\n\tcd %s\n\tgo get %s", message, LoaderState.MainModules.ModRoot(e.ImportingMainModule), e.Path)
}
return fmt.Sprintf("%s; to add it:\n\tgo get %s", message, e.Path)
}
@@ -299,12 +299,12 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M
// Is the package in the standard library?
pathIsStd := search.IsStandardImportPath(path)
if pathIsStd && modindex.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
- for _, mainModule := range MainModules.Versions() {
- if MainModules.InGorootSrc(mainModule) {
- if dir, ok, err := dirInModule(path, MainModules.PathPrefix(mainModule), MainModules.ModRoot(mainModule), true); err != nil {
- return module.Version{}, MainModules.ModRoot(mainModule), dir, nil, err
+ for _, mainModule := range LoaderState.MainModules.Versions() {
+ if LoaderState.MainModules.InGorootSrc(mainModule) {
+ if dir, ok, err := dirInModule(path, LoaderState.MainModules.PathPrefix(mainModule), LoaderState.MainModules.ModRoot(mainModule), true); err != nil {
+ return module.Version{}, LoaderState.MainModules.ModRoot(mainModule), dir, nil, err
} else if ok {
- return mainModule, MainModules.ModRoot(mainModule), dir, nil, nil
+ return mainModule, LoaderState.MainModules.ModRoot(mainModule), dir, nil, nil
}
}
}
@@ -321,10 +321,10 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M
// Everything must be in the main modules or the main module's or workspace's vendor directory.
if cfg.BuildMod == "vendor" {
var mainErr error
- for _, mainModule := range MainModules.Versions() {
- modRoot := MainModules.ModRoot(mainModule)
+ for _, mainModule := range LoaderState.MainModules.Versions() {
+ modRoot := LoaderState.MainModules.ModRoot(mainModule)
if modRoot != "" {
- dir, mainOK, err := dirInModule(path, MainModules.PathPrefix(mainModule), modRoot, true)
+ dir, mainOK, err := dirInModule(path, LoaderState.MainModules.PathPrefix(mainModule), modRoot, true)
if mainErr == nil {
mainErr = err
}
@@ -345,7 +345,7 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M
// vendor/modules.txt does not exist or the user manually added directories to the vendor directory.
// Go 1.23 and later require vendored packages to be present in modules.txt to be imported.
_, ok := vendorPkgModule[path]
- if ok || (gover.Compare(MainModules.GoVersion(), gover.ExplicitModulesTxtImportVersion) < 0) {
+ if ok || (gover.Compare(LoaderState.MainModules.GoVersion(), gover.ExplicitModulesTxtImportVersion) < 0) {
mods = append(mods, vendorPkgModule[path])
dirs = append(dirs, dir)
roots = append(roots, vendorDir)
@@ -471,7 +471,7 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M
// If the module graph is pruned and this is a test-only dependency
// of a package in "all", we didn't necessarily load that file
// when we read the module graph, so do it now to be sure.
- if !skipModFile && cfg.BuildMod != "vendor" && mods[0].Path != "" && !MainModules.Contains(mods[0].Path) {
+ if !skipModFile && cfg.BuildMod != "vendor" && mods[0].Path != "" && !LoaderState.MainModules.Contains(mods[0].Path) {
if _, err := goModSummary(mods[0]); err != nil {
return module.Version{}, "", "", nil, err
}
@@ -511,8 +511,8 @@ func queryImport(ctx context.Context, path string, rs *Requirements) (module.Ver
// To avoid spurious remote fetches, try the latest replacement for each
// module (golang.org/issue/26241).
var mods []module.Version
- if MainModules != nil { // TODO(#48912): Ensure MainModules exists at this point, and remove the check.
- for mp, mv := range MainModules.HighestReplaced() {
+ if LoaderState.MainModules != nil { // TODO(#48912): Ensure MainModules exists at this point, and remove the check.
+ for mp, mv := range LoaderState.MainModules.HighestReplaced() {
if !maybeInModule(path, mp) {
continue
}
@@ -748,7 +748,7 @@ func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFile
// The isLocal return value reports whether the replacement,
// if any, is local to the filesystem.
func fetch(ctx context.Context, mod module.Version) (dir string, isLocal bool, err error) {
- if modRoot := MainModules.ModRoot(mod); modRoot != "" {
+ if modRoot := LoaderState.MainModules.ModRoot(mod); modRoot != "" {
return modRoot, true, nil
}
if r := Replacement(mod); r.Path != "" {
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index dc0d78499b9..28f55a621d6 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -58,7 +58,7 @@ var (
// EnterModule resets MainModules and requirements to refer to just this one module.
func EnterModule(ctx context.Context, enterModroot string) {
- MainModules = nil // reset MainModules
+ LoaderState.MainModules = nil // reset MainModules
requirements = nil
workFilePath = "" // Force module mode
modfetch.Reset()
@@ -73,7 +73,7 @@ func EnterModule(ctx context.Context, enterModroot string) {
// EnterWorkspace will modify the global state they depend on in a non-thread-safe way.
func EnterWorkspace(ctx context.Context) (exit func(), err error) {
// Find the identity of the main module that will be updated before we reset modload state.
- mm := MainModules.mustGetSingleMainModule()
+ mm := LoaderState.MainModules.mustGetSingleMainModule()
// Get the updated modfile we will use for that module.
_, _, updatedmodfile, err := UpdateGoModFromReqs(ctx, WriteOpts{})
if err != nil {
@@ -89,8 +89,8 @@ func EnterWorkspace(ctx context.Context) (exit func(), err error) {
LoadModFile(ctx)
// Update the content of the previous main module, and recompute the requirements.
- *MainModules.ModFile(mm) = *updatedmodfile
- requirements = requirementsFromModFiles(ctx, MainModules.workFile, slices.Collect(maps.Values(MainModules.modFiles)), nil)
+ *LoaderState.MainModules.ModFile(mm) = *updatedmodfile
+ requirements = requirementsFromModFiles(ctx, LoaderState.MainModules.workFile, slices.Collect(maps.Values(LoaderState.MainModules.modFiles)), nil)
return func() {
setState(oldstate)
@@ -294,8 +294,6 @@ func (mms *MainModuleSet) WorkFileReplaceMap() map[module.Version]module.Version
return mms.workFileReplaceMap
}
-var MainModules *MainModuleSet
-
type Root int
const (
@@ -324,7 +322,7 @@ const (
// in go.mod, edit it before loading.
func ModFile() *modfile.File {
Init()
- modFile := MainModules.ModFile(MainModules.mustGetSingleMainModule())
+ modFile := LoaderState.MainModules.ModFile(LoaderState.MainModules.mustGetSingleMainModule())
if modFile == nil {
die()
}
@@ -396,7 +394,7 @@ func setState(s State) State {
RootMode: LoaderState.RootMode,
modRoots: LoaderState.modRoots,
modulesEnabled: cfg.ModulesEnabled,
- mainModules: MainModules,
+ MainModules: LoaderState.MainModules,
requirements: requirements,
}
LoaderState.initialized = s.initialized
@@ -404,7 +402,7 @@ func setState(s State) State {
LoaderState.RootMode = s.RootMode
LoaderState.modRoots = s.modRoots
cfg.ModulesEnabled = s.modulesEnabled
- MainModules = s.mainModules
+ LoaderState.MainModules = s.MainModules
requirements = s.requirements
workFilePath = s.workFilePath
// The modfetch package's global state is used to compute
@@ -431,7 +429,7 @@ type State struct {
// modRoots != nil implies len(modRoots) > 0
modRoots []string
modulesEnabled bool
- mainModules *MainModuleSet
+ MainModules *MainModuleSet
requirements *Requirements
workFilePath string
modfetchState modfetch.State
@@ -628,7 +626,7 @@ func VendorDir() string {
// Even if -mod=vendor, we could be operating with no mod root (and thus no
// vendor directory). As long as there are no dependencies that is expected
// to work. See script/vendor_outside_module.txt.
- modRoot := MainModules.ModRoot(MainModules.mustGetSingleMainModule())
+ modRoot := LoaderState.MainModules.ModRoot(LoaderState.MainModules.mustGetSingleMainModule())
if modRoot == "" {
panic("vendor directory does not exist when in single module mode outside of a module")
}
@@ -914,7 +912,7 @@ func loadModFile(ctx context.Context, opts *PackageOpts) (*Requirements, error)
// make MainModules.Len() == 0 mean that we're in module mode but not inside
// any module.
mainModule := module.Version{Path: "command-line-arguments"}
- MainModules = makeMainModules([]module.Version{mainModule}, []string{""}, []*modfile.File{nil}, []*modFileIndex{nil}, nil)
+ LoaderState.MainModules = makeMainModules([]module.Version{mainModule}, []string{""}, []*modfile.File{nil}, []*modFileIndex{nil}, nil)
var (
goVersion string
pruning modPruning
@@ -925,7 +923,7 @@ func loadModFile(ctx context.Context, opts *PackageOpts) (*Requirements, error)
// Since we are in a workspace, the Go version for the synthetic
// "command-line-arguments" module must not exceed the Go version
// for the workspace.
- goVersion = MainModules.GoVersion()
+ goVersion = LoaderState.MainModules.GoVersion()
pruning = workspace
roots = []module.Version{
mainModule,
@@ -1016,20 +1014,20 @@ func loadModFile(ctx context.Context, opts *PackageOpts) (*Requirements, error)
return nil, errors.Join(errs...)
}
- MainModules = makeMainModules(mainModules, LoaderState.modRoots, modFiles, indices, workFile)
+ LoaderState.MainModules = makeMainModules(mainModules, LoaderState.modRoots, modFiles, indices, workFile)
setDefaultBuildMod() // possibly enable automatic vendoring
rs := requirementsFromModFiles(ctx, workFile, modFiles, opts)
if cfg.BuildMod == "vendor" {
readVendorList(VendorDir())
- versions := MainModules.Versions()
+ versions := LoaderState.MainModules.Versions()
indexes := make([]*modFileIndex, 0, len(versions))
modFiles := make([]*modfile.File, 0, len(versions))
modRoots := make([]string, 0, len(versions))
for _, m := range versions {
- indexes = append(indexes, MainModules.Index(m))
- modFiles = append(modFiles, MainModules.ModFile(m))
- modRoots = append(modRoots, MainModules.ModRoot(m))
+ indexes = append(indexes, LoaderState.MainModules.Index(m))
+ modFiles = append(modFiles, LoaderState.MainModules.ModFile(m))
+ modRoots = append(modRoots, LoaderState.MainModules.ModRoot(m))
}
checkVendorConsistency(indexes, modFiles, modRoots)
rs.initVendor(vendorList)
@@ -1041,7 +1039,7 @@ func loadModFile(ctx context.Context, opts *PackageOpts) (*Requirements, error)
return rs, nil
}
- mainModule := MainModules.mustGetSingleMainModule()
+ mainModule := LoaderState.MainModules.mustGetSingleMainModule()
if rs.hasRedundantRoot() {
// If any module path appears more than once in the roots, we know that the
@@ -1054,7 +1052,7 @@ func loadModFile(ctx context.Context, opts *PackageOpts) (*Requirements, error)
}
}
- if MainModules.Index(mainModule).goVersion == "" && rs.pruning != workspace {
+ if LoaderState.MainModules.Index(mainModule).goVersion == "" && rs.pruning != workspace {
// TODO(#45551): Do something more principled instead of checking
// cfg.CmdName directly here.
if cfg.BuildMod == "mod" && cfg.CmdName != "mod graph" && cfg.CmdName != "mod why" {
@@ -1063,7 +1061,7 @@ func loadModFile(ctx context.Context, opts *PackageOpts) (*Requirements, error)
if opts != nil && opts.TidyGoVersion != "" {
v = opts.TidyGoVersion
}
- addGoStmt(MainModules.ModFile(mainModule), mainModule, v)
+ addGoStmt(LoaderState.MainModules.ModFile(mainModule), mainModule, v)
rs = overrideRoots(ctx, rs, []module.Version{{Path: "go", Version: v}})
// We need to add a 'go' version to the go.mod file, but we must assume
@@ -1156,7 +1154,7 @@ func CreateModFile(ctx context.Context, modPath string) {
fmt.Fprintf(os.Stderr, "go: creating new go.mod: module %s\n", modPath)
modFile := new(modfile.File)
modFile.AddModuleStmt(modPath)
- MainModules = makeMainModules([]module.Version{modFile.Module.Mod}, []string{modRoot}, []*modfile.File{modFile}, []*modFileIndex{nil}, nil)
+ LoaderState.MainModules = makeMainModules([]module.Version{modFile.Module.Mod}, []string{modRoot}, []*modfile.File{modFile}, []*modFileIndex{nil}, nil)
addGoStmt(modFile, modFile.Module.Mod, gover.Local()) // Add the go directive before converted module requirements.
rs := requirementsFromModFiles(ctx, nil, []*modfile.File{modFile}, nil)
@@ -1380,8 +1378,8 @@ func requirementsFromModFiles(ctx context.Context, workFile *modfile.WorkFile, m
var pruning modPruning
if inWorkspaceMode() {
pruning = workspace
- roots = make([]module.Version, len(MainModules.Versions()), 2+len(MainModules.Versions()))
- copy(roots, MainModules.Versions())
+ roots = make([]module.Version, len(LoaderState.MainModules.Versions()), 2+len(LoaderState.MainModules.Versions()))
+ copy(roots, LoaderState.MainModules.Versions())
goVersion := gover.FromGoWork(workFile)
var toolchain string
if workFile.Toolchain != nil {
@@ -1390,12 +1388,12 @@ func requirementsFromModFiles(ctx context.Context, workFile *modfile.WorkFile, m
roots = appendGoAndToolchainRoots(roots, goVersion, toolchain, direct)
direct = directRequirements(modFiles)
} else {
- pruning = pruningForGoVersion(MainModules.GoVersion())
+ pruning = pruningForGoVersion(LoaderState.MainModules.GoVersion())
if len(modFiles) != 1 {
panic(fmt.Errorf("requirementsFromModFiles called with %v modfiles outside workspace mode", len(modFiles)))
}
modFile := modFiles[0]
- roots, direct = rootsFromModFile(MainModules.mustGetSingleMainModule(), modFile, withToolchainRoot)
+ roots, direct = rootsFromModFile(LoaderState.MainModules.mustGetSingleMainModule(), modFile, withToolchainRoot)
}
gover.ModSort(roots)
@@ -1430,7 +1428,7 @@ func rootsFromModFile(m module.Version, modFile *modfile.File, addToolchainRoot
}
roots = make([]module.Version, 0, padding+len(modFile.Require))
for _, r := range modFile.Require {
- if index := MainModules.Index(m); index != nil && index.exclude[r.Mod] {
+ if index := LoaderState.MainModules.Index(m); index != nil && index.exclude[r.Mod] {
if cfg.BuildMod == "mod" {
fmt.Fprintf(os.Stderr, "go: dropping requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
} else {
@@ -1522,12 +1520,12 @@ func setDefaultBuildMod() {
var versionSource string
if inWorkspaceMode() {
versionSource = "go.work"
- if wfg := MainModules.WorkFile().Go; wfg != nil {
+ if wfg := LoaderState.MainModules.WorkFile().Go; wfg != nil {
goVersion = wfg.Version
}
} else {
versionSource = "go.mod"
- index := MainModules.GetSingleIndexOrNil()
+ index := LoaderState.MainModules.GetSingleIndexOrNil()
if index != nil {
goVersion = index.goVersion
}
@@ -1812,12 +1810,12 @@ var errNoChange = errors.New("no update needed")
// UpdateGoModFromReqs returns a modified go.mod file using the current
// requirements. It does not commit these changes to disk.
func UpdateGoModFromReqs(ctx context.Context, opts WriteOpts) (before, after []byte, modFile *modfile.File, err error) {
- if MainModules.Len() != 1 || MainModules.ModRoot(MainModules.Versions()[0]) == "" {
+ if LoaderState.MainModules.Len() != 1 || LoaderState.MainModules.ModRoot(LoaderState.MainModules.Versions()[0]) == "" {
// We aren't in a module, so we don't have anywhere to write a go.mod file.
return nil, nil, nil, errNoChange
}
- mainModule := MainModules.mustGetSingleMainModule()
- modFile = MainModules.ModFile(mainModule)
+ mainModule := LoaderState.MainModules.mustGetSingleMainModule()
+ modFile = LoaderState.MainModules.ModFile(mainModule)
if modFile == nil {
// command-line-arguments has no .mod file to write.
return nil, nil, nil, errNoChange
@@ -1925,7 +1923,7 @@ func commitRequirements(ctx context.Context, opts WriteOpts) (err error) {
return err
}
- index := MainModules.GetSingleIndexOrNil()
+ index := LoaderState.MainModules.GetSingleIndexOrNil()
dirty := index.modFileIsDirty(modFile) || len(opts.DropTools) > 0 || len(opts.AddTools) > 0
if dirty && cfg.BuildMod != "mod" {
// If we're about to fail due to -mod=readonly,
@@ -1946,8 +1944,8 @@ func commitRequirements(ctx context.Context, opts WriteOpts) (err error) {
return nil
}
- mainModule := MainModules.mustGetSingleMainModule()
- modFilePath := modFilePath(MainModules.ModRoot(mainModule))
+ mainModule := LoaderState.MainModules.mustGetSingleMainModule()
+ modFilePath := modFilePath(LoaderState.MainModules.ModRoot(mainModule))
if fsys.Replaced(modFilePath) {
if dirty {
return errors.New("updates to go.mod needed, but go.mod is part of the overlay specified with -overlay")
@@ -1956,7 +1954,7 @@ func commitRequirements(ctx context.Context, opts WriteOpts) (err error) {
}
defer func() {
// At this point we have determined to make the go.mod file on disk equal to new.
- MainModules.SetIndex(mainModule, indexModFile(updatedGoMod, modFile, mainModule, false))
+ LoaderState.MainModules.SetIndex(mainModule, indexModFile(updatedGoMod, modFile, mainModule, false))
// Update go.sum after releasing the side lock and refreshing the index.
// 'go mod init' shouldn't write go.sum, since it will be incomplete.
@@ -2018,7 +2016,7 @@ func keepSums(ctx context.Context, ld *loader, rs *Requirements, which whichSums
// ambiguous import errors the next time we load the package.
keepModSumsForZipSums := true
if ld == nil {
- if gover.Compare(MainModules.GoVersion(), gover.TidyGoModSumVersion) < 0 && cfg.BuildMod != "mod" {
+ if gover.Compare(LoaderState.MainModules.GoVersion(), gover.TidyGoModSumVersion) < 0 && cfg.BuildMod != "mod" {
keepModSumsForZipSums = false
}
} else {
diff --git a/src/cmd/go/internal/modload/list.go b/src/cmd/go/internal/modload/list.go
index 53cb6c2ffe1..b2d071dcf69 100644
--- a/src/cmd/go/internal/modload/list.go
+++ b/src/cmd/go/internal/modload/list.go
@@ -126,7 +126,7 @@ func ListModules(ctx context.Context, args []string, mode ListMode, reuseFile st
func listModules(ctx context.Context, rs *Requirements, args []string, mode ListMode, reuse map[module.Version]*modinfo.ModulePublic) (_ *Requirements, mods []*modinfo.ModulePublic, mgErr error) {
if len(args) == 0 {
var ms []*modinfo.ModulePublic
- for _, m := range MainModules.Versions() {
+ for _, m := range LoaderState.MainModules.Versions() {
if gover.IsToolchain(m.Path) {
continue
}
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index 7fba712f952..54e1da902cf 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -273,7 +273,7 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
if m.Dirs == nil {
matchModRoots := LoaderState.modRoots
if opts.MainModule != (module.Version{}) {
- matchModRoots = []string{MainModules.ModRoot(opts.MainModule)}
+ matchModRoots = []string{LoaderState.MainModules.ModRoot(opts.MainModule)}
}
matchLocalDirs(ctx, matchModRoots, m, rs)
}
@@ -324,7 +324,7 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
matchPackages(ctx, m, opts.Tags, includeStd, mg.BuildList())
case m.Pattern() == "work":
- matchModules := MainModules.Versions()
+ matchModules := LoaderState.MainModules.Versions()
if opts.MainModule != (module.Version{}) {
matchModules = []module.Version{opts.MainModule}
}
@@ -335,12 +335,12 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
// The initial roots are the packages and tools in the main module.
// loadFromRoots will expand that to "all".
m.Errs = m.Errs[:0]
- matchModules := MainModules.Versions()
+ matchModules := LoaderState.MainModules.Versions()
if opts.MainModule != (module.Version{}) {
matchModules = []module.Version{opts.MainModule}
}
matchPackages(ctx, m, opts.Tags, omitStd, matchModules)
- for tool := range MainModules.Tools() {
+ for tool := range LoaderState.MainModules.Tools() {
m.Pkgs = append(m.Pkgs, tool)
}
} else {
@@ -355,7 +355,7 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
}
case m.Pattern() == "tool":
- for tool := range MainModules.Tools() {
+ for tool := range LoaderState.MainModules.Tools() {
m.Pkgs = append(m.Pkgs, tool)
}
default:
@@ -596,13 +596,13 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
}
}
- for _, mod := range MainModules.Versions() {
- modRoot := MainModules.ModRoot(mod)
+ for _, mod := range LoaderState.MainModules.Versions() {
+ modRoot := LoaderState.MainModules.ModRoot(mod)
if modRoot != "" && absDir == modRoot {
if absDir == cfg.GOROOTsrc {
return "", errPkgIsGorootSrc
}
- return MainModules.PathPrefix(mod), nil
+ return LoaderState.MainModules.PathPrefix(mod), nil
}
}
@@ -611,8 +611,8 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
// It's not strictly necessary but helpful to keep the checks.
var pkgNotFoundErr error
pkgNotFoundLongestPrefix := ""
- for _, mainModule := range MainModules.Versions() {
- modRoot := MainModules.ModRoot(mainModule)
+ for _, mainModule := range LoaderState.MainModules.Versions() {
+ modRoot := LoaderState.MainModules.ModRoot(mainModule)
if modRoot != "" && str.HasFilePathPrefix(absDir, modRoot) && !strings.Contains(absDir[len(modRoot):], "@") {
suffix := filepath.ToSlash(str.TrimFilePathPrefix(absDir, modRoot))
if pkg, found := strings.CutPrefix(suffix, "vendor/"); found {
@@ -627,7 +627,7 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
return pkg, nil
}
- mainModulePrefix := MainModules.PathPrefix(mainModule)
+ mainModulePrefix := LoaderState.MainModules.PathPrefix(mainModule)
if mainModulePrefix == "" {
pkg := suffix
if pkg == "builtin" {
@@ -820,7 +820,7 @@ func (mms *MainModuleSet) DirImportPath(ctx context.Context, dir string) (path s
return mms.PathPrefix(v), v
}
if str.HasFilePathPrefix(dir, modRoot) {
- pathPrefix := MainModules.PathPrefix(v)
+ pathPrefix := LoaderState.MainModules.PathPrefix(v)
if pathPrefix > longestPrefix {
longestPrefix = pathPrefix
longestPrefixVersion = v
@@ -1068,7 +1068,7 @@ func (pkg *loadPkg) fromExternalModule() bool {
if pkg.mod.Path == "" {
return false // loaded from the standard library, not a module
}
- return !MainModules.Contains(pkg.mod.Path)
+ return !LoaderState.MainModules.Contains(pkg.mod.Path)
}
var errMissing = errors.New("cannot find package")
@@ -1390,7 +1390,7 @@ func (ld *loader) updateRequirements(ctx context.Context) (changed bool, err err
}
}
}
- if pkg.mod.Version != "" || !MainModules.Contains(pkg.mod.Path) {
+ if pkg.mod.Version != "" || !LoaderState.MainModules.Contains(pkg.mod.Path) {
continue
}
@@ -1587,7 +1587,7 @@ func (ld *loader) resolveMissingImports(ctx context.Context) (modAddedBy map[mod
var ime *ImportMissingError
if errors.As(err, &ime) {
for curstack := pkg.stack; curstack != nil; curstack = curstack.stack {
- if MainModules.Contains(curstack.mod.Path) {
+ if LoaderState.MainModules.Contains(curstack.mod.Path) {
ime.ImportingMainModule = curstack.mod
break
}
@@ -1709,7 +1709,7 @@ func (ld *loader) applyPkgFlags(ctx context.Context, pkg *loadPkg, flags loadPkg
// so it's ok if we call it more than is strictly necessary.
wantTest := false
switch {
- case ld.allPatternIsRoot && MainModules.Contains(pkg.mod.Path):
+ case ld.allPatternIsRoot && LoaderState.MainModules.Contains(pkg.mod.Path):
// We are loading the "all" pattern, which includes packages imported by
// tests in the main module. This package is in the main module, so we
// need to identify the imports of its test even if LoadTests is not set.
@@ -1730,7 +1730,7 @@ func (ld *loader) applyPkgFlags(ctx context.Context, pkg *loadPkg, flags loadPkg
if wantTest {
var testFlags loadPkgFlags
- if MainModules.Contains(pkg.mod.Path) || (ld.allClosesOverTests && new.has(pkgInAll)) {
+ if LoaderState.MainModules.Contains(pkg.mod.Path) || (ld.allClosesOverTests && new.has(pkgInAll)) {
// Tests of packages in the main module are in "all", in the sense that
// they cause the packages they import to also be in "all". So are tests
// of packages in "all" if "all" closes over test dependencies.
@@ -1858,7 +1858,7 @@ func (ld *loader) load(ctx context.Context, pkg *loadPkg) {
var modroot string
pkg.mod, modroot, pkg.dir, pkg.altMods, pkg.err = importFromModules(ctx, pkg.path, ld.requirements, mg, ld.skipImportModFiles)
- if MainModules.Tools()[pkg.path] {
+ if LoaderState.MainModules.Tools()[pkg.path] {
// Tools declared by main modules are always in "all".
// We apply the package flags before returning so that missing
// tool dependencies report an error https://go.dev/issue/70582
@@ -1867,7 +1867,7 @@ func (ld *loader) load(ctx context.Context, pkg *loadPkg) {
if pkg.dir == "" {
return
}
- if MainModules.Contains(pkg.mod.Path) {
+ if LoaderState.MainModules.Contains(pkg.mod.Path) {
// Go ahead and mark pkg as in "all". This provides the invariant that a
// package that is *only* imported by other packages in "all" is always
// marked as such before loading its imports.
@@ -1975,14 +1975,14 @@ func (ld *loader) stdVendor(parentPath, path string) string {
}
if str.HasPathPrefix(parentPath, "cmd") {
- if !ld.VendorModulesInGOROOTSrc || !MainModules.Contains("cmd") {
+ if !ld.VendorModulesInGOROOTSrc || !LoaderState.MainModules.Contains("cmd") {
vendorPath := pathpkg.Join("cmd", "vendor", path)
if _, err := os.Stat(filepath.Join(cfg.GOROOTsrc, filepath.FromSlash(vendorPath))); err == nil {
return vendorPath
}
}
- } else if !ld.VendorModulesInGOROOTSrc || !MainModules.Contains("std") || str.HasPathPrefix(parentPath, "vendor") {
+ } else if !ld.VendorModulesInGOROOTSrc || !LoaderState.MainModules.Contains("std") || str.HasPathPrefix(parentPath, "vendor") {
// If we are outside of the 'std' module, resolve imports from within 'std'
// to the vendor directory.
//
@@ -2067,7 +2067,7 @@ func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements,
fmt.Fprintln(os.Stderr)
goFlag := ""
- if goVersion != MainModules.GoVersion() {
+ if goVersion != LoaderState.MainModules.GoVersion() {
goFlag = " -go=" + goVersion
}
diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go
index 04e204cc984..6b432ed9637 100644
--- a/src/cmd/go/internal/modload/modfile.go
+++ b/src/cmd/go/internal/modload/modfile.go
@@ -156,8 +156,8 @@ var ErrDisallowed = errors.New("disallowed module version")
// CheckExclusions returns an error equivalent to ErrDisallowed if module m is
// excluded by the main module's go.mod file.
func CheckExclusions(ctx context.Context, m module.Version) error {
- for _, mainModule := range MainModules.Versions() {
- if index := MainModules.Index(mainModule); index != nil && index.exclude[m] {
+ for _, mainModule := range LoaderState.MainModules.Versions() {
+ if index := LoaderState.MainModules.Index(mainModule); index != nil && index.exclude[m] {
return module.VersionError(m, errExcluded)
}
}
@@ -349,19 +349,19 @@ func Replacement(mod module.Version) module.Version {
// and the source of the replacement. The replacement is relative to the go.work or go.mod file it appears in.
func replacementFrom(mod module.Version) (r module.Version, modroot string, fromFile string) {
foundFrom, found, foundModRoot := "", module.Version{}, ""
- if MainModules == nil {
+ if LoaderState.MainModules == nil {
return module.Version{}, "", ""
- } else if MainModules.Contains(mod.Path) && mod.Version == "" {
+ } else if LoaderState.MainModules.Contains(mod.Path) && mod.Version == "" {
// Don't replace the workspace version of the main module.
return module.Version{}, "", ""
}
- if _, r, ok := replacement(mod, MainModules.WorkFileReplaceMap()); ok {
+ if _, r, ok := replacement(mod, LoaderState.MainModules.WorkFileReplaceMap()); ok {
return r, "", workFilePath
}
- for _, v := range MainModules.Versions() {
- if index := MainModules.Index(v); index != nil {
+ for _, v := range LoaderState.MainModules.Versions() {
+ if index := LoaderState.MainModules.Index(v); index != nil {
if from, r, ok := replacement(mod, index.replace); ok {
- modRoot := MainModules.ModRoot(v)
+ modRoot := LoaderState.MainModules.ModRoot(v)
if foundModRoot != "" && foundFrom != from && found != r {
base.Errorf("conflicting replacements found for %v in workspace modules defined by %v and %v",
mod, modFilePath(foundModRoot), modFilePath(modRoot))
@@ -378,7 +378,7 @@ func replaceRelativeTo() string {
if workFilePath := WorkFilePath(); workFilePath != "" {
return filepath.Dir(workFilePath)
}
- return MainModules.ModRoot(MainModules.mustGetSingleMainModule())
+ return LoaderState.MainModules.ModRoot(LoaderState.MainModules.mustGetSingleMainModule())
}
// canonicalizeReplacePath ensures that relative, on-disk, replaced module paths
@@ -572,7 +572,7 @@ type retraction struct {
//
// The caller must not modify the returned summary.
func goModSummary(m module.Version) (*modFileSummary, error) {
- if m.Version == "" && !inWorkspaceMode() && MainModules.Contains(m.Path) {
+ if m.Version == "" && !inWorkspaceMode() && LoaderState.MainModules.Contains(m.Path) {
panic("internal error: goModSummary called on a main module")
}
if gover.IsToolchain(m.Path) {
@@ -639,8 +639,8 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
}
}
- for _, mainModule := range MainModules.Versions() {
- if index := MainModules.Index(mainModule); index != nil && len(index.exclude) > 0 {
+ for _, mainModule := range LoaderState.MainModules.Versions() {
+ if index := LoaderState.MainModules.Index(mainModule); index != nil && len(index.exclude) > 0 {
// Drop any requirements on excluded versions.
// Don't modify the cached summary though, since we might need the raw
// summary separately.
@@ -684,7 +684,7 @@ func rawGoModSummary(m module.Version) (*modFileSummary, error) {
}
return &modFileSummary{module: m}, nil
}
- if m.Version == "" && !inWorkspaceMode() && MainModules.Contains(m.Path) {
+ if m.Version == "" && !inWorkspaceMode() && LoaderState.MainModules.Contains(m.Path) {
// Calling rawGoModSummary implies that we are treating m as a module whose
// requirements aren't the roots of the module graph and can't be modified.
//
@@ -697,13 +697,13 @@ func rawGoModSummary(m module.Version) (*modFileSummary, error) {
// If there are no modules in the workspace, we synthesize an empty
// command-line-arguments module, which rawGoModData cannot read a go.mod for.
return &modFileSummary{module: m}, nil
- } else if m.Version == "" && inWorkspaceMode() && MainModules.Contains(m.Path) {
+ } else if m.Version == "" && inWorkspaceMode() && LoaderState.MainModules.Contains(m.Path) {
// When go get uses EnterWorkspace to check that the workspace loads properly,
// it will update the contents of the workspace module's modfile in memory. To use the updated
// contents of the modfile when doing the load, don't read from disk and instead
// recompute a summary using the updated contents of the modfile.
- if mf := MainModules.ModFile(m); mf != nil {
- return summaryFromModFile(m, MainModules.modFiles[m])
+ if mf := LoaderState.MainModules.ModFile(m); mf != nil {
+ return summaryFromModFile(m, LoaderState.MainModules.modFiles[m])
}
}
return rawGoModSummaryCache.Do(m, func() (*modFileSummary, error) {
@@ -783,8 +783,8 @@ func rawGoModData(m module.Version) (name string, data []byte, err error) {
if m.Version == "" {
dir := m.Path
if !filepath.IsAbs(dir) {
- if inWorkspaceMode() && MainModules.Contains(m.Path) {
- dir = MainModules.ModRoot(m)
+ if inWorkspaceMode() && LoaderState.MainModules.Contains(m.Path) {
+ dir = LoaderState.MainModules.ModRoot(m)
} else {
// m is a replacement module with only a file path.
dir = filepath.Join(replaceRelativeTo(), dir)
diff --git a/src/cmd/go/internal/modload/mvs.go b/src/cmd/go/internal/modload/mvs.go
index 8ae2dbff1e8..97e6fe44dd7 100644
--- a/src/cmd/go/internal/modload/mvs.go
+++ b/src/cmd/go/internal/modload/mvs.go
@@ -43,7 +43,7 @@ type mvsReqs struct {
}
func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) {
- if mod.Version == "" && MainModules.Contains(mod.Path) {
+ if mod.Version == "" && LoaderState.MainModules.Contains(mod.Path) {
// Use the build list as it existed when r was constructed, not the current
// global build list.
return r.roots, nil
@@ -112,7 +112,7 @@ func versions(ctx context.Context, path string, allowed AllowedFunc) (versions [
// Since the version of a main module is not found in the version list,
// it has no previous version.
func previousVersion(ctx context.Context, m module.Version) (module.Version, error) {
- if m.Version == "" && MainModules.Contains(m.Path) {
+ if m.Version == "" && LoaderState.MainModules.Contains(m.Path) {
return module.Version{Path: m.Path, Version: "none"}, nil
}
diff --git a/src/cmd/go/internal/modload/query.go b/src/cmd/go/internal/modload/query.go
index 65934b0d69e..94ee8bc9559 100644
--- a/src/cmd/go/internal/modload/query.go
+++ b/src/cmd/go/internal/modload/query.go
@@ -211,7 +211,7 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed
allowed = func(context.Context, module.Version) error { return nil }
}
- if MainModules.Contains(path) && (query == "upgrade" || query == "patch") {
+ if LoaderState.MainModules.Contains(path) && (query == "upgrade" || query == "patch") {
m := module.Version{Path: path}
if err := allowed(ctx, m); err != nil {
return nil, fmt.Errorf("internal error: main module version is not allowed: %w", err)
@@ -700,8 +700,8 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
match = func(mod module.Version, roots []string, isLocal bool) *search.Match {
m := search.NewMatch(pattern)
prefix := mod.Path
- if MainModules.Contains(mod.Path) {
- prefix = MainModules.PathPrefix(module.Version{Path: mod.Path})
+ if LoaderState.MainModules.Contains(mod.Path) {
+ prefix = LoaderState.MainModules.PathPrefix(module.Version{Path: mod.Path})
}
for _, root := range roots {
if _, ok, err := dirInModule(pattern, prefix, root, isLocal); err != nil {
@@ -715,7 +715,7 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
}
var mainModuleMatches []module.Version
- for _, mainModule := range MainModules.Versions() {
+ for _, mainModule := range LoaderState.MainModules.Versions() {
m := match(mainModule, LoaderState.modRoots, true)
if len(m.Pkgs) > 0 {
if query != "upgrade" && query != "patch" {
@@ -842,7 +842,7 @@ func modulePrefixesExcludingTarget(path string) []string {
prefixes := make([]string, 0, strings.Count(path, "/")+1)
mainModulePrefixes := make(map[string]bool)
- for _, m := range MainModules.Versions() {
+ for _, m := range LoaderState.MainModules.Versions() {
mainModulePrefixes[m.Path] = true
}
@@ -905,7 +905,7 @@ func queryPrefixModules(ctx context.Context, candidateModules []string, queryMod
case *PackageNotInModuleError:
// Given the option, prefer to attribute “package not in module”
// to modules other than the main one.
- if noPackage == nil || MainModules.Contains(noPackage.Mod.Path) {
+ if noPackage == nil || LoaderState.MainModules.Contains(noPackage.Mod.Path) {
noPackage = rErr
}
case *NoMatchingVersionError:
@@ -1127,9 +1127,9 @@ func lookupRepo(ctx context.Context, proxy, path string) (repo versionRepo, err
repo = emptyRepo{path: path, err: err}
}
- if MainModules == nil {
+ if LoaderState.MainModules == nil {
return repo, err
- } else if _, ok := MainModules.HighestReplaced()[path]; ok {
+ } else if _, ok := LoaderState.MainModules.HighestReplaced()[path]; ok {
return &replacementRepo{repo: repo}, nil
}
@@ -1186,8 +1186,8 @@ func (rr *replacementRepo) Versions(ctx context.Context, prefix string) (*modfet
}
versions := repoVersions.List
- for _, mm := range MainModules.Versions() {
- if index := MainModules.Index(mm); index != nil && len(index.replace) > 0 {
+ for _, mm := range LoaderState.MainModules.Versions() {
+ if index := LoaderState.MainModules.Index(mm); index != nil && len(index.replace) > 0 {
path := rr.ModulePath()
for m := range index.replace {
if m.Path == path && strings.HasPrefix(m.Version, prefix) && m.Version != "" && !module.IsPseudoVersion(m.Version) {
@@ -1215,8 +1215,8 @@ func (rr *replacementRepo) Stat(ctx context.Context, rev string) (*modfetch.RevI
return info, err
}
var hasReplacements bool
- for _, v := range MainModules.Versions() {
- if index := MainModules.Index(v); index != nil && len(index.replace) > 0 {
+ for _, v := range LoaderState.MainModules.Versions() {
+ if index := LoaderState.MainModules.Index(v); index != nil && len(index.replace) > 0 {
hasReplacements = true
}
}
@@ -1249,7 +1249,7 @@ func (rr *replacementRepo) Latest(ctx context.Context) (*modfetch.RevInfo, error
info, err := rr.repo.Latest(ctx)
path := rr.ModulePath()
- if v, ok := MainModules.HighestReplaced()[path]; ok {
+ if v, ok := LoaderState.MainModules.HighestReplaced()[path]; ok {
if v == "" {
// The only replacement is a wildcard that doesn't specify a version, so
// synthesize a pseudo-version with an appropriate major version and a
@@ -1290,7 +1290,7 @@ type QueryMatchesMainModulesError struct {
}
func (e *QueryMatchesMainModulesError) Error() string {
- if MainModules.Contains(e.Pattern) {
+ if LoaderState.MainModules.Contains(e.Pattern) {
return fmt.Sprintf("can't request version %q of the main module (%s)", e.Query, e.Pattern)
}
diff --git a/src/cmd/go/internal/modload/search.go b/src/cmd/go/internal/modload/search.go
index 9ff9738e281..205db3e8f7c 100644
--- a/src/cmd/go/internal/modload/search.go
+++ b/src/cmd/go/internal/modload/search.go
@@ -171,9 +171,9 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
}
if cfg.BuildMod == "vendor" {
- for _, mod := range MainModules.Versions() {
- if modRoot := MainModules.ModRoot(mod); modRoot != "" {
- walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
+ for _, mod := range LoaderState.MainModules.Versions() {
+ if modRoot := LoaderState.MainModules.ModRoot(mod); modRoot != "" {
+ walkPkgs(modRoot, LoaderState.MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
}
}
if HasModRoot() {
@@ -191,12 +191,12 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
root, modPrefix string
isLocal bool
)
- if MainModules.Contains(mod.Path) {
- if MainModules.ModRoot(mod) == "" {
+ if LoaderState.MainModules.Contains(mod.Path) {
+ if LoaderState.MainModules.ModRoot(mod) == "" {
continue // If there is no main module, we can't search in it.
}
- root = MainModules.ModRoot(mod)
- modPrefix = MainModules.PathPrefix(mod)
+ root = LoaderState.MainModules.ModRoot(mod)
+ modPrefix = LoaderState.MainModules.PathPrefix(mod)
isLocal = true
} else {
var err error
@@ -330,12 +330,12 @@ func parseIgnorePatterns(ctx context.Context, treeCanMatch func(string) bool, mo
}
var modRoot string
var ignorePatterns []string
- if MainModules.Contains(mod.Path) {
- modRoot = MainModules.ModRoot(mod)
+ if LoaderState.MainModules.Contains(mod.Path) {
+ modRoot = LoaderState.MainModules.ModRoot(mod)
if modRoot == "" {
continue
}
- modIndex := MainModules.Index(mod)
+ modIndex := LoaderState.MainModules.Index(mod)
if modIndex == nil {
continue
}
diff --git a/src/cmd/go/internal/modload/vendor.go b/src/cmd/go/internal/modload/vendor.go
index c7fe7319358..d3f055acf64 100644
--- a/src/cmd/go/internal/modload/vendor.go
+++ b/src/cmd/go/internal/modload/vendor.go
@@ -236,8 +236,8 @@ func checkVendorConsistency(indexes []*modFileIndex, modFiles []*modfile.File, m
for _, modFile := range modFiles {
checkReplace(modFile.Replace)
}
- if MainModules.workFile != nil {
- checkReplace(MainModules.workFile.Replace)
+ if LoaderState.MainModules.workFile != nil {
+ checkReplace(LoaderState.MainModules.workFile.Replace)
}
for _, mod := range vendorList {
diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go
index 8bfb3c149b6..7a2963ff29b 100644
--- a/src/cmd/go/internal/test/test.go
+++ b/src/cmd/go/internal/test/test.go
@@ -730,7 +730,7 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) {
// the module cache (or permanently alter the behavior of std tests for all
// users) by writing the failing input to the package's testdata directory.
// (See https://golang.org/issue/48495 and test_fuzz_modcache.txt.)
- mainMods := modload.MainModules
+ mainMods := modload.LoaderState.MainModules
if m := pkgs[0].Module; m != nil && m.Path != "" {
if !mainMods.Contains(m.Path) {
base.Fatalf("cannot use -fuzz flag on package outside the main module")
diff --git a/src/cmd/go/internal/tool/tool.go b/src/cmd/go/internal/tool/tool.go
index ef25d17b54d..e25c06a8f04 100644
--- a/src/cmd/go/internal/tool/tool.go
+++ b/src/cmd/go/internal/tool/tool.go
@@ -163,7 +163,7 @@ func listTools(ctx context.Context) {
modload.InitWorkfile()
modload.LoadModFile(ctx)
- modTools := slices.Sorted(maps.Keys(modload.MainModules.Tools()))
+ modTools := slices.Sorted(maps.Keys(modload.LoaderState.MainModules.Tools()))
for _, tool := range modTools {
fmt.Println(tool)
}
@@ -256,7 +256,7 @@ func loadModTool(ctx context.Context, name string) string {
modload.LoadModFile(ctx)
matches := []string{}
- for tool := range modload.MainModules.Tools() {
+ for tool := range modload.LoaderState.MainModules.Tools() {
if tool == name || defaultExecName(tool) == name {
matches = append(matches, tool)
}
diff --git a/src/cmd/go/internal/workcmd/sync.go b/src/cmd/go/internal/workcmd/sync.go
index 800dd15dd6f..640771d8f75 100644
--- a/src/cmd/go/internal/workcmd/sync.go
+++ b/src/cmd/go/internal/workcmd/sync.go
@@ -60,7 +60,7 @@ func runSync(ctx context.Context, cmd *base.Command, args []string) {
}
mustSelectFor := map[module.Version][]module.Version{}
- mms := modload.MainModules
+ mms := modload.LoaderState.MainModules
opts := modload.PackageOpts{
Tags: imports.AnyTags(),
@@ -131,7 +131,7 @@ func runSync(ctx context.Context, cmd *base.Command, args []string) {
}, "all")
modload.WriteGoMod(ctx, modload.WriteOpts{})
}
- goV = gover.Max(goV, modload.MainModules.GoVersion())
+ goV = gover.Max(goV, modload.LoaderState.MainModules.GoVersion())
}
wf, err := modload.ReadWorkFile(workFilePath)
From cb81270113968408d7cc41c0b1530adb51dd8496 Mon Sep 17 00:00:00 2001
From: Keith Randall
Date: Tue, 7 Oct 2025 10:12:43 -0700
Subject: [PATCH 049/421] Revert "crypto/internal/fips140/subtle: add assembly
implementation of xorBytes for mipsx"
This reverts commit 343e486bfdbf9ca614d3e197afd79ad7ed5fef3e.
Reason for revert: doesn't handle unaligned accesses correctly.
Update #74998
Change-Id: I1d6210eeca9336f2ce311e99944cb270565563aa
Reviewed-on: https://go-review.googlesource.com/c/go/+/709795
Reviewed-by: Cherry Mui
Reviewed-by: Michael Knyszek
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Keith Randall
---
src/crypto/internal/fips140/subtle/xor_asm.go | 2 +-
.../internal/fips140/subtle/xor_generic.go | 2 +-
.../internal/fips140/subtle/xor_mipsx.s | 212 ------------------
3 files changed, 2 insertions(+), 214 deletions(-)
delete mode 100644 src/crypto/internal/fips140/subtle/xor_mipsx.s
diff --git a/src/crypto/internal/fips140/subtle/xor_asm.go b/src/crypto/internal/fips140/subtle/xor_asm.go
index b07239da3e3..e10ea8b4414 100644
--- a/src/crypto/internal/fips140/subtle/xor_asm.go
+++ b/src/crypto/internal/fips140/subtle/xor_asm.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build (amd64 || arm64 || mips || mipsle || mips64 || mips64le || ppc64 || ppc64le || riscv64) && !purego
+//go:build (amd64 || arm64 || mips64 || mips64le || ppc64 || ppc64le || riscv64) && !purego
package subtle
diff --git a/src/crypto/internal/fips140/subtle/xor_generic.go b/src/crypto/internal/fips140/subtle/xor_generic.go
index ed484bc630e..08af84de2a3 100644
--- a/src/crypto/internal/fips140/subtle/xor_generic.go
+++ b/src/crypto/internal/fips140/subtle/xor_generic.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build (!amd64 && !arm64 && !loong64 && !mips && !mipsle && !mips64 && !mips64le && !ppc64 && !ppc64le && !riscv64) || purego
+//go:build (!amd64 && !arm64 && !loong64 && !mips64 && !mips64le && !ppc64 && !ppc64le && !riscv64) || purego
package subtle
diff --git a/src/crypto/internal/fips140/subtle/xor_mipsx.s b/src/crypto/internal/fips140/subtle/xor_mipsx.s
deleted file mode 100644
index 1a6b3f409dd..00000000000
--- a/src/crypto/internal/fips140/subtle/xor_mipsx.s
+++ /dev/null
@@ -1,212 +0,0 @@
-// 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.
-
-//go:build (mips || mipsle) && !purego
-
-#include "textflag.h"
-
-// func xorBytes(dst, a, b *byte, n int)
-TEXT ·xorBytes(SB), NOSPLIT|NOFRAME, $0
- MOVW dst+0(FP), R1
- MOVW a+4(FP), R2
- MOVW b+8(FP), R3
- MOVW n+12(FP), R4
-
- SGTU $64, R4, R5 // R5 = 1 if (64 > R4)
- BNE R5, xor_32_check
-xor_64:
- MOVW (R2), R6
- MOVW 4(R2), R7
- MOVW 8(R2), R8
- MOVW 12(R2), R9
- MOVW (R3), R10
- MOVW 4(R3), R11
- MOVW 8(R3), R12
- MOVW 12(R3), R13
- XOR R6, R10
- XOR R7, R11
- XOR R8, R12
- XOR R9, R13
- MOVW R10, (R1)
- MOVW R11, 4(R1)
- MOVW R12, 8(R1)
- MOVW R13, 12(R1)
- MOVW 16(R2), R6
- MOVW 20(R2), R7
- MOVW 24(R2), R8
- MOVW 28(R2), R9
- MOVW 16(R3), R10
- MOVW 20(R3), R11
- MOVW 24(R3), R12
- MOVW 28(R3), R13
- XOR R6, R10
- XOR R7, R11
- XOR R8, R12
- XOR R9, R13
- MOVW R10, 16(R1)
- MOVW R11, 20(R1)
- MOVW R12, 24(R1)
- MOVW R13, 28(R1)
- MOVW 32(R2), R6
- MOVW 36(R2), R7
- MOVW 40(R2), R8
- MOVW 44(R2), R9
- MOVW 32(R3), R10
- MOVW 36(R3), R11
- MOVW 40(R3), R12
- MOVW 44(R3), R13
- XOR R6, R10
- XOR R7, R11
- XOR R8, R12
- XOR R9, R13
- MOVW R10, 32(R1)
- MOVW R11, 36(R1)
- MOVW R12, 40(R1)
- MOVW R13, 44(R1)
- MOVW 48(R2), R6
- MOVW 52(R2), R7
- MOVW 56(R2), R8
- MOVW 60(R2), R9
- MOVW 48(R3), R10
- MOVW 52(R3), R11
- MOVW 56(R3), R12
- MOVW 60(R3), R13
- XOR R6, R10
- XOR R7, R11
- XOR R8, R12
- XOR R9, R13
- MOVW R10, 48(R1)
- MOVW R11, 52(R1)
- MOVW R12, 56(R1)
- MOVW R13, 60(R1)
- ADD $64, R2
- ADD $64, R3
- ADD $64, R1
- SUB $64, R4
- SGTU $64, R4, R5
- BEQ R0, R5, xor_64
- BEQ R0, R4, end
-
-xor_32_check:
- SGTU $32, R4, R5
- BNE R5, xor_16_check
-xor_32:
- MOVW (R2), R6
- MOVW 4(R2), R7
- MOVW 8(R2), R8
- MOVW 12(R2), R9
- MOVW (R3), R10
- MOVW 4(R3), R11
- MOVW 8(R3), R12
- MOVW 12(R3), R13
- XOR R6, R10
- XOR R7, R11
- XOR R8, R12
- XOR R9, R13
- MOVW R10, (R1)
- MOVW R11, 4(R1)
- MOVW R12, 8(R1)
- MOVW R13, 12(R1)
- MOVW 16(R2), R6
- MOVW 20(R2), R7
- MOVW 24(R2), R8
- MOVW 28(R2), R9
- MOVW 16(R3), R10
- MOVW 20(R3), R11
- MOVW 24(R3), R12
- MOVW 28(R3), R13
- XOR R6, R10
- XOR R7, R11
- XOR R8, R12
- XOR R9, R13
- MOVW R10, 16(R1)
- MOVW R11, 20(R1)
- MOVW R12, 24(R1)
- MOVW R13, 28(R1)
- ADD $32, R2
- ADD $32, R3
- ADD $32, R1
- SUB $32, R4
- BEQ R0, R4, end
-
-xor_16_check:
- SGTU $16, R4, R5
- BNE R5, xor_8_check
-xor_16:
- MOVW (R2), R6
- MOVW 4(R2), R7
- MOVW 8(R2), R8
- MOVW 12(R2), R9
- MOVW (R3), R10
- MOVW 4(R3), R11
- MOVW 8(R3), R12
- MOVW 12(R3), R13
- XOR R6, R10
- XOR R7, R11
- XOR R8, R12
- XOR R9, R13
- MOVW R10, (R1)
- MOVW R11, 4(R1)
- MOVW R12, 8(R1)
- MOVW R13, 12(R1)
- ADD $16, R2
- ADD $16, R3
- ADD $16, R1
- SUB $16, R4
- BEQ R0, R4, end
-
-xor_8_check:
- SGTU $8, R4, R5
- BNE R5, xor_4_check
-xor_8:
- MOVW (R2), R6
- MOVW 4(R2), R7
- MOVW (R3), R8
- MOVW 4(R3), R9
- XOR R6, R8
- XOR R7, R9
- MOVW R8, (R1)
- MOVW R9, 4(R1)
- ADD $8, R1
- ADD $8, R2
- ADD $8, R3
- SUB $8, R4
- BEQ R0, R4, end
-
-xor_4_check:
- SGTU $4, R4, R5
- BNE R5, xor_2_check
-xor_4:
- MOVW (R2), R6
- MOVW (R3), R7
- XOR R6, R7
- MOVW R7, (R1)
- ADD $4, R2
- ADD $4, R3
- ADD $4, R1
- SUB $4, R4
- BEQ R0, R4, end
-
-xor_2_check:
- SGTU $2, R4, R5
- BNE R5, xor_1
-xor_2:
- MOVH (R2), R6
- MOVH (R3), R7
- XOR R6, R7
- MOVH R7, (R1)
- ADD $2, R2
- ADD $2, R3
- ADD $2, R1
- SUB $2, R4
- BEQ R0, R4, end
-
-xor_1:
- MOVB (R2), R6
- MOVB (R3), R7
- XOR R6, R7
- MOVB R7, (R1)
-
-end:
- RET
From a1661e776f57602b4d4470389a0246f9784fd722 Mon Sep 17 00:00:00 2001
From: Keith Randall
Date: Tue, 7 Oct 2025 10:15:43 -0700
Subject: [PATCH 050/421] Revert "crypto/internal/fips140/subtle: add assembly
implementation of xorBytes for mips64x"
This reverts commit 49d6777d87a0abb3eda032da95eff024156835f7.
Reason for revert: doesn't handle unaligned accesses correctly
Fixes #74998
Change-Id: Ia272245a6a2a91b305d411207430bad660ee355b
Reviewed-on: https://go-review.googlesource.com/c/go/+/709757
Reviewed-by: Keith Randall
Reviewed-by: Cherry Mui
LUCI-TryBot-Result: Go LUCI
---
src/crypto/internal/fips140/subtle/xor_asm.go | 2 +-
.../internal/fips140/subtle/xor_generic.go | 2 +-
.../internal/fips140/subtle/xor_mips64x.s | 153 ------------------
3 files changed, 2 insertions(+), 155 deletions(-)
delete mode 100644 src/crypto/internal/fips140/subtle/xor_mips64x.s
diff --git a/src/crypto/internal/fips140/subtle/xor_asm.go b/src/crypto/internal/fips140/subtle/xor_asm.go
index e10ea8b4414..bb85aefef40 100644
--- a/src/crypto/internal/fips140/subtle/xor_asm.go
+++ b/src/crypto/internal/fips140/subtle/xor_asm.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build (amd64 || arm64 || mips64 || mips64le || ppc64 || ppc64le || riscv64) && !purego
+//go:build (amd64 || arm64 || ppc64 || ppc64le || riscv64) && !purego
package subtle
diff --git a/src/crypto/internal/fips140/subtle/xor_generic.go b/src/crypto/internal/fips140/subtle/xor_generic.go
index 08af84de2a3..0b31eec6019 100644
--- a/src/crypto/internal/fips140/subtle/xor_generic.go
+++ b/src/crypto/internal/fips140/subtle/xor_generic.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build (!amd64 && !arm64 && !loong64 && !mips64 && !mips64le && !ppc64 && !ppc64le && !riscv64) || purego
+//go:build (!amd64 && !arm64 && !loong64 && !ppc64 && !ppc64le && !riscv64) || purego
package subtle
diff --git a/src/crypto/internal/fips140/subtle/xor_mips64x.s b/src/crypto/internal/fips140/subtle/xor_mips64x.s
deleted file mode 100644
index e580235914a..00000000000
--- a/src/crypto/internal/fips140/subtle/xor_mips64x.s
+++ /dev/null
@@ -1,153 +0,0 @@
-// 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.
-
-//go:build (mips64 || mips64le) && !purego
-
-#include "textflag.h"
-
-// func xorBytes(dst, a, b *byte, n int)
-TEXT ·xorBytes(SB), NOSPLIT|NOFRAME, $0
- MOVV dst+0(FP), R1
- MOVV a+8(FP), R2
- MOVV b+16(FP), R3
- MOVV n+24(FP), R4
-
-xor_64_check:
- SGTU $64, R4, R5 // R5 = 1 if (64 > R4)
- BNE R5, xor_32_check
-xor_64:
- MOVV (R2), R6
- MOVV 8(R2), R7
- MOVV 16(R2), R8
- MOVV 24(R2), R9
- MOVV (R3), R10
- MOVV 8(R3), R11
- MOVV 16(R3), R12
- MOVV 24(R3), R13
- XOR R6, R10
- XOR R7, R11
- XOR R8, R12
- XOR R9, R13
- MOVV R10, (R1)
- MOVV R11, 8(R1)
- MOVV R12, 16(R1)
- MOVV R13, 24(R1)
- MOVV 32(R2), R6
- MOVV 40(R2), R7
- MOVV 48(R2), R8
- MOVV 56(R2), R9
- MOVV 32(R3), R10
- MOVV 40(R3), R11
- MOVV 48(R3), R12
- MOVV 56(R3), R13
- XOR R6, R10
- XOR R7, R11
- XOR R8, R12
- XOR R9, R13
- MOVV R10, 32(R1)
- MOVV R11, 40(R1)
- MOVV R12, 48(R1)
- MOVV R13, 56(R1)
- ADDV $64, R2
- ADDV $64, R3
- ADDV $64, R1
- SUBV $64, R4
- SGTU $64, R4, R5
- BEQ R0, R5, xor_64
- BEQ R0, R4, end
-
-xor_32_check:
- SGTU $32, R4, R5
- BNE R5, xor_16_check
-xor_32:
- MOVV (R2), R6
- MOVV 8(R2), R7
- MOVV 16(R2), R8
- MOVV 24(R2), R9
- MOVV (R3), R10
- MOVV 8(R3), R11
- MOVV 16(R3), R12
- MOVV 24(R3), R13
- XOR R6, R10
- XOR R7, R11
- XOR R8, R12
- XOR R9, R13
- MOVV R10, (R1)
- MOVV R11, 8(R1)
- MOVV R12, 16(R1)
- MOVV R13, 24(R1)
- ADDV $32, R2
- ADDV $32, R3
- ADDV $32, R1
- SUBV $32, R4
- BEQ R0, R4, end
-
-xor_16_check:
- SGTU $16, R4, R5
- BNE R5, xor_8_check
-xor_16:
- MOVV (R2), R6
- MOVV 8(R2), R7
- MOVV (R3), R8
- MOVV 8(R3), R9
- XOR R6, R8
- XOR R7, R9
- MOVV R8, (R1)
- MOVV R9, 8(R1)
- ADDV $16, R2
- ADDV $16, R3
- ADDV $16, R1
- SUBV $16, R4
- BEQ R0, R4, end
-
-xor_8_check:
- SGTU $8, R4, R5
- BNE R5, xor_4_check
-xor_8:
- MOVV (R2), R6
- MOVV (R3), R7
- XOR R6, R7
- MOVV R7, (R1)
- ADDV $8, R1
- ADDV $8, R2
- ADDV $8, R3
- SUBV $8, R4
- BEQ R0, R4, end
-
-xor_4_check:
- SGTU $4, R4, R5
- BNE R5, xor_2_check
-xor_4:
- MOVW (R2), R6
- MOVW (R3), R7
- XOR R6, R7
- MOVW R7, (R1)
- ADDV $4, R2
- ADDV $4, R3
- ADDV $4, R1
- SUBV $4, R4
- BEQ R0, R4, end
-
-xor_2_check:
- SGTU $2, R4, R5
- BNE R5, xor_1
-xor_2:
- MOVH (R2), R6
- MOVH (R3), R7
- XOR R6, R7
- MOVH R7, (R1)
- ADDV $2, R2
- ADDV $2, R3
- ADDV $2, R1
- SUBV $2, R4
- BEQ R0, R4, end
-
-xor_1:
- MOVB (R2), R6
- MOVB (R3), R7
- XOR R6, R7
- MOVB R7, (R1)
-
-end:
- RET
From 162392773085d4cc12072200853a0424117983c0 Mon Sep 17 00:00:00 2001
From: Ian Alexander
Date: Wed, 20 Aug 2025 20:14:59 -0400
Subject: [PATCH 051/421] cmd/go: refactor usage of `requirements`
This commit refactors usage of the global variable `requirements` to
the global LoaderState field of the same name.
This commit is part of the overall effort to eliminate global
modloader state.
[git-generate]
cd src/cmd/go/internal/modload
rf 'ex { requirements -> LoaderState.requirements }'
rf 'add State.MainModules \
// requirements is the requirement graph for the main module.\
//\
// It is always non-nil if the main module'\\\''s go.mod file has been\
// loaded.\
//\
// This variable should only be read from the loadModFile\
// function, and should only be written in the loadModFile and\
// commitRequirements functions. All other functions that need or\
// produce a *Requirements should accept and/or return an explicit\
// parameter.'
rf 'rm requirements'
Change-Id: I9d7d1d301a9e89f9214ce632fa5b656dd2940f39
Reviewed-on: https://go-review.googlesource.com/c/go/+/698061
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Michael Matloob
Reviewed-by: Michael Matloob
---
src/cmd/go/internal/modload/buildlist.go | 14 +-----
src/cmd/go/internal/modload/init.go | 55 ++++++++++++++----------
src/cmd/go/internal/modload/list.go | 2 +-
src/cmd/go/internal/modload/load.go | 8 ++--
4 files changed, 40 insertions(+), 39 deletions(-)
diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go
index 8afea0b205c..b73cb43d0dc 100644
--- a/src/cmd/go/internal/modload/buildlist.go
+++ b/src/cmd/go/internal/modload/buildlist.go
@@ -85,16 +85,6 @@ type cachedGraph struct {
err error // If err is non-nil, mg may be incomplete (but must still be non-nil).
}
-// requirements is the requirement graph for the main module.
-//
-// It is always non-nil if the main module's go.mod file has been loaded.
-//
-// This variable should only be read from the loadModFile function, and should
-// only be written in the loadModFile and commitRequirements functions.
-// All other functions that need or produce a *Requirements should
-// accept and/or return an explicit parameter.
-var requirements *Requirements
-
func mustHaveGoRoot(roots []module.Version) {
for _, m := range roots {
if m.Path == "go" {
@@ -589,7 +579,7 @@ func LoadModGraph(ctx context.Context, goVersion string) (*ModuleGraph, error) {
if err != nil {
return nil, err
}
- requirements = rs
+ LoaderState.requirements = rs
return mg, nil
}
@@ -654,7 +644,7 @@ func EditBuildList(ctx context.Context, add, mustSelect []module.Version) (chang
if err != nil {
return false, err
}
- requirements = rs
+ LoaderState.requirements = rs
return changed, nil
}
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 28f55a621d6..1c8aa379d3c 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -59,7 +59,7 @@ var (
// EnterModule resets MainModules and requirements to refer to just this one module.
func EnterModule(ctx context.Context, enterModroot string) {
LoaderState.MainModules = nil // reset MainModules
- requirements = nil
+ LoaderState.requirements = nil
workFilePath = "" // Force module mode
modfetch.Reset()
@@ -90,7 +90,7 @@ func EnterWorkspace(ctx context.Context) (exit func(), err error) {
// Update the content of the previous main module, and recompute the requirements.
*LoaderState.MainModules.ModFile(mm) = *updatedmodfile
- requirements = requirementsFromModFiles(ctx, LoaderState.MainModules.workFile, slices.Collect(maps.Values(LoaderState.MainModules.modFiles)), nil)
+ LoaderState.requirements = requirementsFromModFiles(ctx, LoaderState.MainModules.workFile, slices.Collect(maps.Values(LoaderState.MainModules.modFiles)), nil)
return func() {
setState(oldstate)
@@ -395,7 +395,7 @@ func setState(s State) State {
modRoots: LoaderState.modRoots,
modulesEnabled: cfg.ModulesEnabled,
MainModules: LoaderState.MainModules,
- requirements: requirements,
+ requirements: LoaderState.requirements,
}
LoaderState.initialized = s.initialized
LoaderState.ForceUseModules = s.ForceUseModules
@@ -403,7 +403,7 @@ func setState(s State) State {
LoaderState.modRoots = s.modRoots
cfg.ModulesEnabled = s.modulesEnabled
LoaderState.MainModules = s.MainModules
- requirements = s.requirements
+ LoaderState.requirements = s.requirements
workFilePath = s.workFilePath
// The modfetch package's global state is used to compute
// the go.sum file, so save and restore it along with the
@@ -430,9 +430,20 @@ type State struct {
modRoots []string
modulesEnabled bool
MainModules *MainModuleSet
- requirements *Requirements
- workFilePath string
- modfetchState modfetch.State
+
+ // requirements is the requirement graph for the main module.
+ //
+ // It is always non-nil if the main module's go.mod file has been
+ // loaded.
+ //
+ // This variable should only be read from the loadModFile
+ // function, and should only be written in the loadModFile and
+ // commitRequirements functions. All other functions that need or
+ // produce a *Requirements should accept and/or return an explicit
+ // parameter.
+ requirements *Requirements
+ workFilePath string
+ modfetchState modfetch.State
}
func NewState() *State { return &State{} }
@@ -869,8 +880,8 @@ func LoadModFile(ctx context.Context) *Requirements {
}
func loadModFile(ctx context.Context, opts *PackageOpts) (*Requirements, error) {
- if requirements != nil {
- return requirements, nil
+ if LoaderState.requirements != nil {
+ return LoaderState.requirements, nil
}
Init()
@@ -939,14 +950,14 @@ func loadModFile(ctx context.Context, opts *PackageOpts) (*Requirements, error)
}
}
rawGoVersion.Store(mainModule, goVersion)
- requirements = newRequirements(pruning, roots, direct)
+ LoaderState.requirements = newRequirements(pruning, roots, direct)
if cfg.BuildMod == "vendor" {
// For issue 56536: Some users may have GOFLAGS=-mod=vendor set.
// Make sure it behaves as though the fake module is vendored
// with no dependencies.
- requirements.initVendor(nil)
+ LoaderState.requirements.initVendor(nil)
}
- return requirements, nil
+ return LoaderState.requirements, nil
}
var modFiles []*modfile.File
@@ -1035,7 +1046,7 @@ func loadModFile(ctx context.Context, opts *PackageOpts) (*Requirements, error)
if inWorkspaceMode() {
// We don't need to update the mod file so return early.
- requirements = rs
+ LoaderState.requirements = rs
return rs, nil
}
@@ -1081,8 +1092,8 @@ func loadModFile(ctx context.Context, opts *PackageOpts) (*Requirements, error)
}
}
- requirements = rs
- return requirements, nil
+ LoaderState.requirements = rs
+ return LoaderState.requirements, nil
}
func errWorkTooOld(gomod string, wf *modfile.WorkFile, goVers string) error {
@@ -1162,7 +1173,7 @@ func CreateModFile(ctx context.Context, modPath string) {
if err != nil {
base.Fatal(err)
}
- requirements = rs
+ LoaderState.requirements = rs
if err := commitRequirements(ctx, WriteOpts{}); err != nil {
base.Fatal(err)
}
@@ -1801,7 +1812,7 @@ type WriteOpts struct {
// WriteGoMod writes the current build list back to go.mod.
func WriteGoMod(ctx context.Context, opts WriteOpts) error {
- requirements = LoadModFile(ctx)
+ LoaderState.requirements = LoadModFile(ctx)
return commitRequirements(ctx, opts)
}
@@ -1828,7 +1839,7 @@ func UpdateGoModFromReqs(ctx context.Context, opts WriteOpts) (before, after []b
var list []*modfile.Require
toolchain := ""
goVersion := ""
- for _, m := range requirements.rootModules {
+ for _, m := range LoaderState.requirements.rootModules {
if m.Path == "go" {
goVersion = m.Version
continue
@@ -1839,7 +1850,7 @@ func UpdateGoModFromReqs(ctx context.Context, opts WriteOpts) (before, after []b
}
list = append(list, &modfile.Require{
Mod: m,
- Indirect: !requirements.direct[m.Path],
+ Indirect: !LoaderState.requirements.direct[m.Path],
})
}
@@ -1913,7 +1924,7 @@ func commitRequirements(ctx context.Context, opts WriteOpts) (err error) {
if inWorkspaceMode() {
// go.mod files aren't updated in workspace mode, but we still want to
// update the go.work.sum file.
- return modfetch.WriteGoSum(ctx, keepSums(ctx, loaded, requirements, addBuildListZipSums), mustHaveCompleteRequirements())
+ return modfetch.WriteGoSum(ctx, keepSums(ctx, loaded, LoaderState.requirements, addBuildListZipSums), mustHaveCompleteRequirements())
}
_, updatedGoMod, modFile, err := UpdateGoModFromReqs(ctx, opts)
if err != nil {
@@ -1937,7 +1948,7 @@ func commitRequirements(ctx context.Context, opts WriteOpts) (err error) {
// Don't write go.mod, but write go.sum in case we added or trimmed sums.
// 'go mod init' shouldn't write go.sum, since it will be incomplete.
if cfg.CmdName != "mod init" {
- if err := modfetch.WriteGoSum(ctx, keepSums(ctx, loaded, requirements, addBuildListZipSums), mustHaveCompleteRequirements()); err != nil {
+ if err := modfetch.WriteGoSum(ctx, keepSums(ctx, loaded, LoaderState.requirements, addBuildListZipSums), mustHaveCompleteRequirements()); err != nil {
return err
}
}
@@ -1960,7 +1971,7 @@ func commitRequirements(ctx context.Context, opts WriteOpts) (err error) {
// 'go mod init' shouldn't write go.sum, since it will be incomplete.
if cfg.CmdName != "mod init" {
if err == nil {
- err = modfetch.WriteGoSum(ctx, keepSums(ctx, loaded, requirements, addBuildListZipSums), mustHaveCompleteRequirements())
+ err = modfetch.WriteGoSum(ctx, keepSums(ctx, loaded, LoaderState.requirements, addBuildListZipSums), mustHaveCompleteRequirements())
}
}
}()
diff --git a/src/cmd/go/internal/modload/list.go b/src/cmd/go/internal/modload/list.go
index b2d071dcf69..b66e73a112c 100644
--- a/src/cmd/go/internal/modload/list.go
+++ b/src/cmd/go/internal/modload/list.go
@@ -109,7 +109,7 @@ func ListModules(ctx context.Context, args []string, mode ListMode, reuseFile st
}
if err == nil {
- requirements = rs
+ LoaderState.requirements = rs
// TODO(#61605): The extra ListU clause fixes a problem with Go 1.21rc3
// where "go mod tidy" and "go list -m -u all" fight over whether the go.sum
// should be considered up-to-date. The fix for now is to always treat the
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index 54e1da902cf..413de8148fb 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -455,7 +455,7 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
if opts.TidyDiff {
cfg.BuildMod = "readonly"
loaded = ld
- requirements = loaded.requirements
+ LoaderState.requirements = loaded.requirements
currentGoMod, updatedGoMod, _, err := UpdateGoModFromReqs(ctx, WriteOpts{})
if err != nil {
base.Fatal(err)
@@ -466,7 +466,7 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
// Dropping compatibility for 1.16 may result in a strictly smaller go.sum.
// Update the keep map with only the loaded.requirements.
if gover.Compare(compatVersion, "1.16") > 0 {
- keep = keepSums(ctx, loaded, requirements, addBuildListZipSums)
+ keep = keepSums(ctx, loaded, LoaderState.requirements, addBuildListZipSums)
}
currentGoSum, tidyGoSum := modfetch.TidyGoSum(keep)
goSumDiff := diff.Diff("current/go.sum", currentGoSum, "tidy/go.sum", tidyGoSum)
@@ -505,7 +505,7 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
// to call WriteGoMod itself) or if ResolveMissingImports is false (the
// command wants to examine the package graph as-is).
loaded = ld
- requirements = loaded.requirements
+ LoaderState.requirements = loaded.requirements
for _, pkg := range ld.pkgs {
if !pkg.isTest() {
@@ -788,7 +788,7 @@ func ImportFromFiles(ctx context.Context, gofiles []string) {
return roots
},
})
- requirements = loaded.requirements
+ LoaderState.requirements = loaded.requirements
if !ExplicitWriteGoMod {
if err := commitRequirements(ctx, WriteOpts{}); err != nil {
From bb1ca7ae81ea8ca49a2773ace8ccff8fbc7f4dfd Mon Sep 17 00:00:00 2001
From: Damien Neil
Date: Fri, 15 Aug 2025 15:24:05 -0700
Subject: [PATCH 052/421] cmd/go, testing: add TB.ArtifactDir and -artifacts
flag
Add TB.ArtifactDir, which returns a directory for a test to store
output files in. Add a -artifacts testflag which enables persistent
storage of artifacts in the output directory (-outputdir, or the
current directory by default).
Fixes #71287
Change-Id: I5f6515a6cd6c103f88588f4c033d5ea11ffd0c3c
Reviewed-on: https://go-review.googlesource.com/c/go/+/696399
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Alan Donovan
---
api/next/71287.txt | 4 +
doc/next/6-stdlib/99-minor/testing/71287.md | 18 ++
src/cmd/go/alldocs.go | 12 +-
src/cmd/go/internal/load/test.go | 9 +
src/cmd/go/internal/test/flagdefs.go | 1 +
src/cmd/go/internal/test/test.go | 13 +-
src/cmd/go/internal/test/testflag.go | 4 +-
src/cmd/internal/test2json/test2json.go | 24 ++-
src/testing/internal/testdeps/deps.go | 6 +
src/testing/testing.go | 207 +++++++++++++++-----
src/testing/testing_test.go | 106 +++++++++-
11 files changed, 333 insertions(+), 71 deletions(-)
create mode 100644 api/next/71287.txt
create mode 100644 doc/next/6-stdlib/99-minor/testing/71287.md
diff --git a/api/next/71287.txt b/api/next/71287.txt
new file mode 100644
index 00000000000..c1e09a1f523
--- /dev/null
+++ b/api/next/71287.txt
@@ -0,0 +1,4 @@
+pkg testing, method (*B) ArtifactDir() string #71287
+pkg testing, method (*F) ArtifactDir() string #71287
+pkg testing, method (*T) ArtifactDir() string #71287
+pkg testing, type TB interface, ArtifactDir() string #71287
diff --git a/doc/next/6-stdlib/99-minor/testing/71287.md b/doc/next/6-stdlib/99-minor/testing/71287.md
new file mode 100644
index 00000000000..82cac638101
--- /dev/null
+++ b/doc/next/6-stdlib/99-minor/testing/71287.md
@@ -0,0 +1,18 @@
+The new methods [T.ArtifactDir], [B.ArtifactDir], and [F.ArtifactDir]
+return a directory in which to write test output files (artifacts).
+
+When the `-artifacts` flag is provided to `go test`,
+this directory will be located under the output directory
+(specified with `-outputdir`, or the current directory by default).
+Otherwise, artifacts are stored in a temporary directory
+which is removed after the test completes.
+
+The first call to `ArtifactDir` when `-artifacts` is provided
+writes the location of the directory to the test log.
+
+For example, in a test named `TestArtifacts`,
+`t.ArtifactDir()` emits:
+
+```
+=== ARTIFACTS Test /path/to/artifact/dir
+```
diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go
index 19b48f0579b..51f2223283b 100644
--- a/src/cmd/go/alldocs.go
+++ b/src/cmd/go/alldocs.go
@@ -3244,6 +3244,10 @@
// The following flags are recognized by the 'go test' command and
// control the execution of any test:
//
+// -artifacts
+// Save test artifacts in the directory specified by -outputdir.
+// See 'go doc testing.T.ArtifactDir'.
+//
// -bench regexp
// Run only those benchmarks matching a regular expression.
// By default, no benchmarks are run.
@@ -3338,6 +3342,10 @@
// This will only list top-level tests. No subtest or subbenchmarks will be
// shown.
//
+// -outputdir directory
+// Place output files from profiling and test artifacts in the
+// specified directory, by default the directory in which "go test" is running.
+//
// -parallel n
// Allow parallel execution of test functions that call t.Parallel, and
// fuzz targets that call t.Parallel when running the seed corpus.
@@ -3449,10 +3457,6 @@
// Sample 1 in n stack traces of goroutines holding a
// contended mutex.
//
-// -outputdir directory
-// Place output files from profiling in the specified directory,
-// by default the directory in which "go test" is running.
-//
// -trace trace.out
// Write an execution trace to the specified file before exiting.
//
diff --git a/src/cmd/go/internal/load/test.go b/src/cmd/go/internal/load/test.go
index f895e3a2461..9849ee138a5 100644
--- a/src/cmd/go/internal/load/test.go
+++ b/src/cmd/go/internal/load/test.go
@@ -649,6 +649,14 @@ func (t *testFuncs) ImportPath() string {
return pkg
}
+func (t *testFuncs) ModulePath() string {
+ m := t.Package.Module
+ if m == nil {
+ return ""
+ }
+ return m.Path
+}
+
// Covered returns a string describing which packages are being tested for coverage.
// If the covered package is the same as the tested package, it returns the empty string.
// Otherwise it is a comma-separated human-readable list of packages beginning with
@@ -836,6 +844,7 @@ func init() {
testdeps.CoverMarkProfileEmittedFunc = cfile.MarkProfileEmitted
{{end}}
+ testdeps.ModulePath = {{.ModulePath | printf "%q"}}
testdeps.ImportPath = {{.ImportPath | printf "%q"}}
}
diff --git a/src/cmd/go/internal/test/flagdefs.go b/src/cmd/go/internal/test/flagdefs.go
index 8aa0bfc2bf3..b8b4bf649e4 100644
--- a/src/cmd/go/internal/test/flagdefs.go
+++ b/src/cmd/go/internal/test/flagdefs.go
@@ -9,6 +9,7 @@ package test
// passFlagToTest contains the flags that should be forwarded to
// the test binary with the prefix "test.".
var passFlagToTest = map[string]bool{
+ "artifacts": true,
"bench": true,
"benchmem": true,
"benchtime": true,
diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go
index 7a2963ff29b..15ffc618c65 100644
--- a/src/cmd/go/internal/test/test.go
+++ b/src/cmd/go/internal/test/test.go
@@ -192,6 +192,10 @@ and -show_bytes options of pprof control how the information is presented.
The following flags are recognized by the 'go test' command and
control the execution of any test:
+ -artifacts
+ Save test artifacts in the directory specified by -outputdir.
+ See 'go doc testing.T.ArtifactDir'.
+
-bench regexp
Run only those benchmarks matching a regular expression.
By default, no benchmarks are run.
@@ -286,6 +290,10 @@ control the execution of any test:
This will only list top-level tests. No subtest or subbenchmarks will be
shown.
+ -outputdir directory
+ Place output files from profiling and test artifacts in the
+ specified directory, by default the directory in which "go test" is running.
+
-parallel n
Allow parallel execution of test functions that call t.Parallel, and
fuzz targets that call t.Parallel when running the seed corpus.
@@ -397,10 +405,6 @@ profile the tests during execution:
Sample 1 in n stack traces of goroutines holding a
contended mutex.
- -outputdir directory
- Place output files from profiling in the specified directory,
- by default the directory in which "go test" is running.
-
-trace trace.out
Write an execution trace to the specified file before exiting.
@@ -540,6 +544,7 @@ See the documentation of the testing package for more information.
}
var (
+ testArtifacts bool // -artifacts flag
testBench string // -bench flag
testC bool // -c flag
testCoverPkgs []*load.Package // -coverpkg flag
diff --git a/src/cmd/go/internal/test/testflag.go b/src/cmd/go/internal/test/testflag.go
index 983e8f56e9a..fc2b22cb56a 100644
--- a/src/cmd/go/internal/test/testflag.go
+++ b/src/cmd/go/internal/test/testflag.go
@@ -44,6 +44,7 @@ func init() {
// some of them so that cmd/go knows what to do with the test output, or knows
// to build the test in a way that supports the use of the flag.
+ cf.BoolVar(&testArtifacts, "artifacts", false, "")
cf.StringVar(&testBench, "bench", "", "")
cf.Bool("benchmem", false, "")
cf.String("benchtime", "", "")
@@ -392,7 +393,8 @@ func testFlags(args []string) (packageNames, passToTest []string) {
// directory, but 'go test' defaults it to the working directory of the 'go'
// command. Set it explicitly if it is needed due to some other flag that
// requests output.
- if testProfile() != "" && !outputDirSet {
+ needOutputDir := testProfile() != "" || testArtifacts
+ if needOutputDir && !outputDirSet {
injectedFlags = append(injectedFlags, "-test.outputdir="+testOutputDir.getAbs())
}
diff --git a/src/cmd/internal/test2json/test2json.go b/src/cmd/internal/test2json/test2json.go
index d08ef389f82..f28051e1771 100644
--- a/src/cmd/internal/test2json/test2json.go
+++ b/src/cmd/internal/test2json/test2json.go
@@ -38,6 +38,7 @@ type event struct {
FailedBuild string `json:",omitempty"`
Key string `json:",omitempty"`
Value string `json:",omitempty"`
+ Path string `json:",omitempty"`
}
// textBytes is a hack to get JSON to emit a []byte as a string
@@ -180,6 +181,7 @@ var (
[]byte("=== FAIL "),
[]byte("=== SKIP "),
[]byte("=== ATTR "),
+ []byte("=== ARTIFACTS "),
}
reports = [][]byte{
@@ -251,7 +253,6 @@ func (c *Converter) handleInputLine(line []byte) {
// "=== RUN "
// "=== PAUSE "
// "=== CONT "
- actionColon := false
origLine := line
ok := false
indent := 0
@@ -273,7 +274,6 @@ func (c *Converter) handleInputLine(line []byte) {
}
for _, magic := range reports {
if bytes.HasPrefix(line, magic) {
- actionColon = true
ok = true
break
}
@@ -296,16 +296,11 @@ func (c *Converter) handleInputLine(line []byte) {
return
}
- // Parse out action and test name.
- i := 0
- if actionColon {
- i = bytes.IndexByte(line, ':') + 1
- }
- if i == 0 {
- i = len(updates[0])
- }
- action := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(string(line[4:i])), ":"))
- name := strings.TrimSpace(string(line[i:]))
+ // Parse out action and test name from "=== ACTION: Name".
+ action, name, _ := strings.Cut(string(line[len("=== "):]), " ")
+ action = strings.TrimSuffix(action, ":")
+ action = strings.ToLower(action)
+ name = strings.TrimSpace(name)
e := &event{Action: action}
if line[0] == '-' { // PASS or FAIL report
@@ -336,7 +331,10 @@ func (c *Converter) handleInputLine(line []byte) {
c.output.write(origLine)
return
}
- if action == "attr" {
+ switch action {
+ case "artifacts":
+ name, e.Path, _ = strings.Cut(name, " ")
+ case "attr":
var rest string
name, rest, _ = strings.Cut(name, " ")
e.Key, e.Value, _ = strings.Cut(rest, " ")
diff --git a/src/testing/internal/testdeps/deps.go b/src/testing/internal/testdeps/deps.go
index 6f42d4722ca..5ab377daeb6 100644
--- a/src/testing/internal/testdeps/deps.go
+++ b/src/testing/internal/testdeps/deps.go
@@ -66,6 +66,12 @@ func (TestDeps) ImportPath() string {
return ImportPath
}
+var ModulePath string
+
+func (TestDeps) ModulePath() string {
+ return ModulePath
+}
+
// testLog implements testlog.Interface, logging actions by package os.
type testLog struct {
mu sync.Mutex
diff --git a/src/testing/testing.go b/src/testing/testing.go
index 3f764465493..0d1d08ca89a 100644
--- a/src/testing/testing.go
+++ b/src/testing/testing.go
@@ -420,7 +420,6 @@ import (
"sync/atomic"
"time"
"unicode"
- "unicode/utf8"
_ "unsafe" // for linkname
)
@@ -456,6 +455,7 @@ func Init() {
// this flag lets "go test" tell the binary to write the files in the directory where
// the "go test" command is run.
outputDir = flag.String("test.outputdir", "", "write profiles to `dir`")
+ artifacts = flag.Bool("test.artifacts", false, "store test artifacts in test.,outputdir")
// Report as tests are run; default is silent for success.
flag.Var(&chatty, "test.v", "verbose: print additional output")
count = flag.Uint("test.count", 1, "run tests and benchmarks `n` times")
@@ -489,6 +489,7 @@ var (
short *bool
failFast *bool
outputDir *string
+ artifacts *bool
chatty chattyFlag
count *uint
coverProfile *string
@@ -516,6 +517,7 @@ var (
cpuList []int
testlogFile *os.File
+ artifactDir string
numFailed atomic.Uint32 // number of test failures
@@ -653,15 +655,17 @@ type common struct {
runner string // Function name of tRunner running the test.
isParallel bool // Whether the test is parallel.
- parent *common
- level int // Nesting depth of test or benchmark.
- creator []uintptr // If level > 0, the stack trace at the point where the parent called t.Run.
- name string // Name of test or benchmark.
- start highPrecisionTime // Time test or benchmark started
- duration time.Duration
- barrier chan bool // To signal parallel subtests they may start. Nil when T.Parallel is not present (B) or not usable (when fuzzing).
- signal chan bool // To signal a test is done.
- sub []*T // Queue of subtests to be run in parallel.
+ parent *common
+ level int // Nesting depth of test or benchmark.
+ creator []uintptr // If level > 0, the stack trace at the point where the parent called t.Run.
+ modulePath string
+ importPath string
+ name string // Name of test or benchmark.
+ start highPrecisionTime // Time test or benchmark started
+ duration time.Duration
+ barrier chan bool // To signal parallel subtests they may start. Nil when T.Parallel is not present (B) or not usable (when fuzzing).
+ signal chan bool // To signal a test is done.
+ sub []*T // Queue of subtests to be run in parallel.
lastRaceErrors atomic.Int64 // Max value of race.Errors seen during the test or its subtests.
raceErrorLogged atomic.Bool
@@ -671,6 +675,10 @@ type common struct {
tempDirErr error
tempDirSeq int32
+ artifactDirOnce sync.Once
+ artifactDir string
+ artifactDirErr error
+
ctx context.Context
cancelCtx context.CancelFunc
}
@@ -879,6 +887,7 @@ func fmtDuration(d time.Duration) string {
// TB is the interface common to [T], [B], and [F].
type TB interface {
+ ArtifactDir() string
Attr(key, value string)
Cleanup(func())
Error(args ...any)
@@ -1313,6 +1322,96 @@ func (c *common) Cleanup(f func()) {
c.cleanups = append(c.cleanups, fn)
}
+// ArtifactDir returns a directory in which the test should store output files.
+// When the -artifacts flag is provided, this directory is located
+// under the output directory. Otherwise, ArtifactDir returns a temporary directory
+// that is removed after the test completes.
+//
+// Each test or subtest within each test package has a unique artifact directory.
+// Repeated calls to ArtifactDir in the same test or subtest return the same directory.
+// Subtest outputs are not located under the parent test's output directory.
+func (c *common) ArtifactDir() string {
+ c.checkFuzzFn("ArtifactDir")
+ c.artifactDirOnce.Do(func() {
+ c.artifactDir, c.artifactDirErr = c.makeArtifactDir()
+ })
+ if c.artifactDirErr != nil {
+ c.Fatalf("ArtifactDir: %v", c.artifactDirErr)
+ }
+ return c.artifactDir
+}
+
+func hashString(s string) (h uint64) {
+ // FNV, used here to avoid a dependency on maphash.
+ for i := 0; i < len(s); i++ {
+ h ^= uint64(s[i])
+ h *= 1099511628211
+ }
+ return
+}
+
+// makeArtifactDir creates the artifact directory for a test.
+// The artifact directory is:
+//
+//