From 96307fb52ff61e791cec8d0578237c450f5c4eea Mon Sep 17 00:00:00 2001 From: Julian Soreavis Date: Fri, 10 Jul 2026 18:45:29 +0000 Subject: [PATCH 1/8] math: document Nextafter and Nextafter32 special cases for signed zero Nextafter and Nextafter32 have distinct behavior when the starting value is a signed zero, which the special-case lists did not mention: towards the oppositely-signed zero the result keeps the starting sign, and towards any nonzero y the result is the smallest subnormal with the sign of y. Document these cases; the behavior is unchanged. Fixes #42613 Change-Id: I8c6f0f3fc821d285d6870e76edf72391b124d350 GitHub-Last-Rev: 34eb6cc9b47720f3d330b1d385a16fd62adae107 GitHub-Pull-Request: golang/go#80359 Reviewed-on: https://go-review.googlesource.com/c/go/+/799400 Reviewed-by: Keith Randall LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Michael Pratt Auto-Submit: Keith Randall Reviewed-by: Keith Randall --- src/math/nextafter.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/math/nextafter.go b/src/math/nextafter.go index ec18d542d9c037..6325ca4600cab2 100644 --- a/src/math/nextafter.go +++ b/src/math/nextafter.go @@ -8,7 +8,8 @@ package math // // Special cases are: // -// Nextafter32(x, x) = x +// Nextafter32(x, y) = x when x == y +// Nextafter32(0, y) = ±SmallestNonzeroFloat32 towards y, for y ≠ 0 // Nextafter32(NaN, y) = NaN // Nextafter32(x, NaN) = NaN func Nextafter32(x, y float32) (r float32) { @@ -31,7 +32,8 @@ func Nextafter32(x, y float32) (r float32) { // // Special cases are: // -// Nextafter(x, x) = x +// Nextafter(x, y) = x when x == y +// Nextafter(0, y) = ±SmallestNonzeroFloat64 towards y, for y ≠ 0 // Nextafter(NaN, y) = NaN // Nextafter(x, NaN) = NaN func Nextafter(x, y float64) (r float64) { From 94635846472f7c84adb90f42523dd5a780aac0e0 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Wed, 1 Jul 2026 18:44:21 +0800 Subject: [PATCH 2/8] cmd/compile: remove no-op ABIConfig.Copy ABIConfig.Copy became a no-op after the register cache was removed. Its only caller still treated the returned configuration as a fresh copy, even though Copy simply returned its receiver. Remove Copy, use the shared ABI0 configuration directly, and update the stale comments describing copied ABI configurations. Change-Id: I23e51c197811ec409473737b570232e459ec79b4 Reviewed-on: https://go-review.googlesource.com/c/go/+/796120 Auto-Submit: Keith Randall Reviewed-by: Keith Randall Reviewed-by: Michael Pratt Reviewed-by: Keith Randall LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/cmd/compile/internal/abi/abiutils.go | 7 ------- src/cmd/compile/internal/ssa/func.go | 4 ++-- src/cmd/compile/internal/ssagen/ssa.go | 5 ++--- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/cmd/compile/internal/abi/abiutils.go b/src/cmd/compile/internal/abi/abiutils.go index 7acab36e8df3e2..0e1cf88757ea21 100644 --- a/src/cmd/compile/internal/abi/abiutils.go +++ b/src/cmd/compile/internal/abi/abiutils.go @@ -278,13 +278,6 @@ func NewABIConfig(iRegsCount, fRegsCount int, offsetForLocals int64, which uint8 return &ABIConfig{offsetForLocals: offsetForLocals, regAmounts: RegAmounts{iRegsCount, fRegsCount}, which: obj.ABI(which)} } -// Copy returns config. -// -// TODO(mdempsky): Remove. -func (config *ABIConfig) Copy() *ABIConfig { - return config -} - // Which returns the ABI number func (config *ABIConfig) Which() obj.ABI { return config.which diff --git a/src/cmd/compile/internal/ssa/func.go b/src/cmd/compile/internal/ssa/func.go index 690e2f033d67e6..1c67691547a99c 100644 --- a/src/cmd/compile/internal/ssa/func.go +++ b/src/cmd/compile/internal/ssa/func.go @@ -36,8 +36,8 @@ type Func struct { HTMLWriter *HTMLWriter // html writer, for debugging PrintOrHtmlSSA bool // true if GOSSAFUNC matches, true even if fe.Log() (spew phase results to stdout) is false. There's an odd dependence on this in debug.go for method logf. ruleMatches map[string]int // number of times countRule was called during compilation for any given string - ABI0 *abi.ABIConfig // A copy, for no-sync access - ABI1 *abi.ABIConfig // A copy, for no-sync access + ABI0 *abi.ABIConfig // ABI configuration for ABI0 + ABI1 *abi.ABIConfig // ABI configuration for ABIInternal ABISelf *abi.ABIConfig // ABI for function being compiled ABIDefault *abi.ABIConfig // ABI for rtcall and other no-parsed-signature/pragma functions. diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index ff56c9964f4543..7d523e56928d79 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -223,12 +223,11 @@ func InitTables() { // any ABI wrapper that is present is nosplit, hence a precise // stack map is not needed there (the parameters survive only long // enough to call the wrapped assembly function). -// This always returns a freshly copied ABI. func AbiForBodylessFuncStackMap(fn *ir.Func) *abi.ABIConfig { - return ssaConfig.ABI0.Copy() // No idea what races will result, be safe + return ssaConfig.ABI0 } -// abiForFunc implements ABI policy for a function, but does not return a copy of the ABI. +// abiForFunc implements ABI policy for a function. // Passing a nil function returns the default ABI based on experiment configuration. func abiForFunc(fn *ir.Func, abi0, abi1 *abi.ABIConfig) *abi.ABIConfig { if buildcfg.Experiment.RegabiArgs { From 25de5ebdcd710cd9dcb50ec6b03026fe3bc2a336 Mon Sep 17 00:00:00 2001 From: Faizan Shaik Date: Thu, 2 Jul 2026 18:55:30 +0000 Subject: [PATCH 3/8] internal/poll: document why zero-byte reads do not wait for readability The TODO asked whether a zero-byte Read should wait for readability. CL 22031 proposed that behavior and was abandoned. Blocking would change the behavior of existing callers, and the netpoller's edge-triggered notifications alone cannot detect data that is already buffered. Replace the stale TODO with the outcome. Updates #15735 Change-Id: Iad2087b123db58e6bf531f81f8411f363a5b2a1a GitHub-Last-Rev: 0c3e22b1d972dbd31a6a9155c6483a8fdbb25b88 GitHub-Pull-Request: golang/go#80244 Reviewed-on: https://go-review.googlesource.com/c/go/+/796620 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Michael Pratt Auto-Submit: Ian Lance Taylor Reviewed-by: Ian Lance Taylor Reviewed-by: David Chase --- src/internal/poll/fd_unix.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/internal/poll/fd_unix.go b/src/internal/poll/fd_unix.go index f56173524d721c..bebf01a86385bc 100644 --- a/src/internal/poll/fd_unix.go +++ b/src/internal/poll/fd_unix.go @@ -148,7 +148,12 @@ func (fd *FD) Read(p []byte) (int, error) { // without trying (but after acquiring the readLock). // Otherwise syscall.Read returns 0, nil which looks like // io.EOF. - // TODO(bradfitz): make it wait for readability? (Issue 15735) + // + // Waiting for readability instead was proposed in + // go.dev/cl/22031 and abandoned. Blocking would change + // the behavior of existing callers, and the netpoller's + // edge-triggered notifications alone cannot detect data + // that is already buffered. See go.dev/issue/15735. return 0, nil } if err := fd.pd.prepareRead(fd.isFile); err != nil { From 0b9d31758bbed0d2518e362fbb1c15175453a93e Mon Sep 17 00:00:00 2001 From: Julian Soreavis Date: Thu, 9 Jul 2026 21:42:45 +0000 Subject: [PATCH 4/8] os: document Process.Pid Pid is an exported field but had no doc comment. Fixes #36726 Change-Id: I4eb213c004f7bcfcab758a56554b489ce6eb7305 GitHub-Last-Rev: d00a4922260efce244b8d402b09e136833d19532 GitHub-Pull-Request: golang/go#80337 Reviewed-on: https://go-review.googlesource.com/c/go/+/799062 Reviewed-by: Ian Lance Taylor Reviewed-by: Michael Pratt Auto-Submit: Michael Pratt Reviewed-by: David Chase LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/os/exec.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/os/exec.go b/src/os/exec.go index 8b164ad6673bb9..6208603b7f64f9 100644 --- a/src/os/exec.go +++ b/src/os/exec.go @@ -41,6 +41,7 @@ const ( // Process stores the information about a process created by [StartProcess]. type Process struct { + // Pid is the operating system process ID. Pid int // state contains the atomic process state. From ba26848a8e63dcac36f18a71fb673de7abc46d83 Mon Sep 17 00:00:00 2001 From: Sean Rogers Date: Sun, 28 Jun 2026 10:12:08 -0600 Subject: [PATCH 5/8] os: document that ReadFile returns *PathError errors ReadFile opens the file with Open and reads it with File.Read, both of which return errors of type *PathError. Document this on ReadFile, as is already done for Open and other functions in the package, so callers know they can inspect the error (for example with errors.As) without reading the source. Fixes #80111 Change-Id: Ifb4b10127c2cf8daae661d136d5d9edbc4613e8a Reviewed-on: https://go-review.googlesource.com/c/go/+/795160 Auto-Submit: Sean Liao Reviewed-by: David Chase LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Michael Pratt Reviewed-by: Maciej Szeptuch Reviewed-by: Sean Liao --- src/os/file.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/os/file.go b/src/os/file.go index e23a08b4f0aac6..764ce12c0e5368 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -867,6 +867,7 @@ func (dir dirFS) join(name string) (string, error) { // A successful call returns err == nil, not err == EOF. // Because ReadFile reads the whole file, it does not treat an EOF from Read // as an error to be reported. +// If there is an error, it will be of type [*PathError]. func ReadFile(name string) ([]byte, error) { f, err := Open(name) if err != nil { From 92efe23bc0ac046b2678745bc74941ba68691ba7 Mon Sep 17 00:00:00 2001 From: Julian Soreavis Date: Fri, 10 Jul 2026 14:33:38 +0000 Subject: [PATCH 6/8] text/template, html/template: document calling Funcs after parsing Funcs was documented only as "It must be called before the template is parsed", which suggests it cannot be called afterward. In fact a function used in the template must be registered before parsing, but Funcs may be called again later (including after Clone) to replace a function of the same name, and the replacement takes effect when the template is executed. Fixes #34680 Change-Id: I819bc3bcb7452b2fd0bd08d2e97e737493944029 GitHub-Last-Rev: a521e2c82a9e8f3ed09c3ec5bcbf98f33b0d1933 GitHub-Pull-Request: golang/go#80356 Reviewed-on: https://go-review.googlesource.com/c/go/+/799340 Reviewed-by: Sean Liao Reviewed-by: David Chase Auto-Submit: Sean Liao LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Michael Pratt Reviewed-by: Rob Pike --- src/html/template/template.go | 8 +++++--- src/text/template/template.go | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/html/template/template.go b/src/html/template/template.go index 2440fecbf9eaa9..55989b06d082a1 100644 --- a/src/html/template/template.go +++ b/src/html/template/template.go @@ -331,10 +331,12 @@ func (t *Template) Name() string { type FuncMap = template.FuncMap // Funcs adds the elements of the argument map to the template's function map. -// It must be called before the template is parsed. +// Any function used in the template must be added before the template is +// parsed. Funcs may be called more than once, including after parsing (for +// example, after [Template.Clone]), to replace a function of the same name; +// the replacement is used when the template is executed. // It panics if a value in the map is not a function with appropriate return -// type. However, it is legal to overwrite elements of the map. The return -// value is the template, so calls can be chained. +// type. The return value is the template, so calls can be chained. func (t *Template) Funcs(funcMap FuncMap) *Template { t.text.Funcs(template.FuncMap(funcMap)) return t diff --git a/src/text/template/template.go b/src/text/template/template.go index 9ae5a6ca5bfa21..2020219e9c3c40 100644 --- a/src/text/template/template.go +++ b/src/text/template/template.go @@ -167,11 +167,13 @@ func (t *Template) Delims(left, right string) *Template { } // Funcs adds the elements of the argument map to the template's function map. -// It must be called before the template is parsed. +// Any function used in the template must be added before the template is +// parsed. Funcs may be called more than once, including after parsing (for +// example, after [Template.Clone]), to replace a function of the same name; +// the replacement is used when the template is executed. // It panics if a value in the map is not a function with appropriate return // type or if the name cannot be used syntactically as a function in a template. -// It is legal to overwrite elements of the map. The return value is the template, -// so calls can be chained. +// The return value is the template, so calls can be chained. func (t *Template) Funcs(funcMap FuncMap) *Template { t.init() t.muFuncs.Lock() From eb18b3bb9ec51d5e6052373b445ae1e7af2f1f93 Mon Sep 17 00:00:00 2001 From: Julian Soreavis Date: Sat, 11 Jul 2026 18:32:41 +0000 Subject: [PATCH 7/8] html/template: document that Tree is updated after the first execution Fixes #43064 Change-Id: Ie693c05563e5694b8ba57eebd459d3c0a2d2a4d6 GitHub-Last-Rev: 91709d7908533ee5441565b3f0c19a3d14087b1b GitHub-Pull-Request: golang/go#80336 Reviewed-on: https://go-review.googlesource.com/c/go/+/799061 Auto-Submit: Sean Liao Reviewed-by: Sean Liao Reviewed-by: Michael Pratt Reviewed-by: David Chase LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/html/template/template.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/html/template/template.go b/src/html/template/template.go index 55989b06d082a1..097f3dfa2d5e77 100644 --- a/src/html/template/template.go +++ b/src/html/template/template.go @@ -25,7 +25,8 @@ type Template struct { // we need to keep our version of the name space and the underlying // template's in sync. text *template.Template - // The underlying template's parse tree, updated to be HTML-safe. + // The underlying template's parse tree, updated to be HTML-safe + // after the first execution. Tree *parse.Tree *nameSpace // common to all associated templates } From a1298407792546b9e93393c903c26be9e87e8e3b Mon Sep 17 00:00:00 2001 From: "khr@golang.org" Date: Tue, 7 Jul 2026 09:51:36 -0700 Subject: [PATCH 8/8] runtime: be sure to scan small frame introduced by (*sigctxt).pushCall On arm64, the contents of R30 are saved in this 16-byte frame (when doing async preemption). But we don't scan that little frame. So any pointer that was in R30 will not get scanned. So the object it points to may get collected prematurely. Fixes #80188 The test here is pretty quick (~1/3 sec) and fails ~50% of the time. Change-Id: I46a7a6a25fabeb4f15dca96ee6fdd5e99d4c6323 Reviewed-on: https://go-review.googlesource.com/c/go/+/797521 Reviewed-by: Cherry Mui Reviewed-by: Keith Randall LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Auto-Submit: Keith Randall --- src/runtime/mgcmark.go | 9 ++ src/runtime/traceback.go | 1 + test/fixedbugs/issue80188.go | 238 +++++++++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 test/fixedbugs/issue80188.go diff --git a/src/runtime/mgcmark.go b/src/runtime/mgcmark.go index f58b98bd7f93e1..edbdb3cc1056d9 100644 --- a/src/runtime/mgcmark.go +++ b/src/runtime/mgcmark.go @@ -1094,6 +1094,15 @@ func scanframeworker(frame *stkframe, state *stackScanState, gcw *gcWork) { if frame.varp != 0 { size := frame.varp - frame.sp if size > 0 { + isSigPanic := frame.fn.valid() && frame.fn.funcID == abi.FuncID_sigpanic + if usesLR && (isSigPanic || isAsyncPreempt || isDebugCall) { + // Also include the small frame injected by + // (*sigctxt).pushCall. This is the same bump + // as the SP bump used in (*unwinder).next. + // We need this to ensure LR is scanned (for when + // it contains a pointer-y non-PC). See issue 80188. + size += alignUp(sys.MinFrameSize, sys.StackAlign) + } scanConservative(frame.sp, size, nil, gcw, state) } } diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go index 348f5c17033a10..e05075432df93a 100644 --- a/src/runtime/traceback.go +++ b/src/runtime/traceback.go @@ -501,6 +501,7 @@ func (u *unwinder) next() { // before faking a call. if usesLR && injectedCall { x := *(*uintptr)(unsafe.Pointer(frame.sp)) + // same as the size bump used in scanframeworker. frame.sp += alignUp(sys.MinFrameSize, sys.StackAlign) f = findfunc(frame.pc) frame.fn = f diff --git a/test/fixedbugs/issue80188.go b/test/fixedbugs/issue80188.go new file mode 100644 index 00000000000000..de2aa9f10e1f75 --- /dev/null +++ b/test/fixedbugs/issue80188.go @@ -0,0 +1,238 @@ +// run + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "runtime" + +type T struct { + a, b, c, d int +} + +func (t *T) sum() int { + return t.a + t.b + t.c + t.d +} + +//go:noinline +func newT(x int) *T { + t := new(T) + t.a = x + t.b = x + t.c = x + t.d = x + return t +} + +//go:noinline +func intptr() *int { + return &g +} + +var g int + +//go:noinline +func f(x int) []*T { + const N = 40 + var q [N]*T + for i := range N { + q[i] = newT(x) + } + var a [2]*int + for i := range a { + a[i] = intptr() + } + + // reserve 2 registers we will later drop. + b := a[0] + c := a[1] + + // Load pointers into registers (or spill slots) + p0 := q[0] + p1 := q[1] + p2 := q[2] + p3 := q[3] + p4 := q[4] + p5 := q[5] + p6 := q[6] + p7 := q[7] + p8 := q[8] + p9 := q[9] + p10 := q[10] + p11 := q[11] + p12 := q[12] + p13 := q[13] + p14 := q[14] + p15 := q[15] + p16 := q[16] + p17 := q[17] + p18 := q[18] + p19 := q[19] + p20 := q[20] + p21 := q[21] + p22 := q[22] + p23 := q[23] + p24 := q[24] + p25 := q[25] + p26 := q[26] + p27 := q[27] + p28 := q[28] + p29 := q[29] + p30 := q[30] + p31 := q[31] + p32 := q[32] + p33 := q[33] + p34 := q[34] + p35 := q[35] + p36 := q[36] + p37 := q[37] + p38 := q[38] + p39 := q[39] + + // q is dead at this point. The only live reference to + // the objects allocated by newT are in the pXX variables. + // But if async preempted, we will scan the frame conservatively, + // which will find dead entries in q. Remove them. + q[0] = nil + q[1] = nil + q[2] = nil + q[3] = nil + q[4] = nil + q[5] = nil + q[6] = nil + q[7] = nil + q[8] = nil + q[9] = nil + q[10] = nil + q[11] = nil + q[12] = nil + q[13] = nil + q[14] = nil + q[15] = nil + q[16] = nil + q[17] = nil + q[18] = nil + q[19] = nil + q[20] = nil + q[21] = nil + q[22] = nil + q[23] = nil + q[24] = nil + q[25] = nil + q[26] = nil + q[27] = nil + q[28] = nil + q[29] = nil + q[30] = nil + q[31] = nil + q[32] = nil + q[33] = nil + q[34] = nil + q[35] = nil + q[36] = nil + q[37] = nil + q[38] = nil + q[39] = nil + + // Some pXX is held in R30. + // If async preemption happens here, the pointer in R30 + // will not get scanned and might cause the pointed-to + // object to be collected prematurely. It does not live + // anywhere else in this frame, and we will not scan + // this frame again later. + // + // Note that the write barrier has to be off for some of + // the newT calls for objects not to be allocated black. + // But it must be on here to get an async preempt. + // It must be off again by the final copy to not get put + // in a write barrier buffer somewhere. + + // delay here to encourage async preemption + *b = 0 + *c = 0 + // The 2 registers holding b,c are now free. They + // can be used to implement this loop without spilling + // R30 to get a free register. + for range 10000 { + } + + // Store registers back to stack memory (with no write barriers) + var z [N]*T + z[0] = p0 + z[1] = p1 + z[2] = p2 + z[3] = p3 + z[4] = p4 + z[5] = p5 + z[6] = p6 + z[7] = p7 + z[8] = p8 + z[9] = p9 + z[10] = p10 + z[11] = p11 + z[12] = p12 + z[13] = p13 + z[14] = p14 + z[15] = p15 + z[16] = p16 + z[17] = p17 + z[18] = p18 + z[19] = p19 + z[20] = p20 + z[21] = p21 + z[22] = p22 + z[23] = p23 + z[24] = p24 + z[25] = p25 + z[26] = p26 + z[27] = p27 + z[28] = p28 + z[29] = p29 + z[30] = p30 + z[31] = p31 + z[32] = p32 + z[33] = p33 + z[34] = p34 + z[35] = p35 + z[36] = p36 + z[37] = p37 + z[38] = p38 + z[39] = p39 + + // Delay again, hoping GC finishes before we publish + // the unscanned pointer via the write barrier. + for range 10000 { + } + + // Copy results to heap to return. + r := new([N]*T) + *r = z + return r[:] +} + +func main() { + runtime.GOMAXPROCS(2) + c := make(chan bool) + for range 4 { + go goroutine(c) + } + for range 4 { + <-c + } +} +func goroutine(c chan bool) { + var all [][]*T + for x := range 10000 { + all = append(all, f(x)) + } + for x, a := range all { + for _, t := range a { + if t.a != x || t.b != x || t.c != x || t.d != x { + panic("bad") + } + } + } + c <- true +}