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 { diff --git a/src/html/template/template.go b/src/html/template/template.go index 2440fecbf9eaa9..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 } @@ -331,10 +332,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/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 { 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) { 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. 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 { 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/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() 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 +}