Skip to content
7 changes: 0 additions & 7 deletions src/cmd/compile/internal/abi/abiutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/compile/internal/ssa/func.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
5 changes: 2 additions & 3 deletions src/cmd/compile/internal/ssagen/ssa.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
11 changes: 7 additions & 4 deletions src/html/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion src/internal/poll/fd_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 4 additions & 2 deletions src/math/nextafter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions src/os/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/os/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions src/runtime/mgcmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
1 change: 1 addition & 0 deletions src/runtime/traceback.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions src/text/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
238 changes: 238 additions & 0 deletions test/fixedbugs/issue80188.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading