1
0
mirror of https://github.com/golang/go synced 2024-11-26 05:07:59 -07:00

sync/atomic: add typed atomic values

These implementations will inline to the lower-level primitives,
but they hide the underlying values so that all accesses are
forced to use the atomic APIs. They also allow the use of shorter
names (methods instead of functions) at call sites, making code
more readable.

Pointer[T] also avoids conversions using unsafe.Pointer at call sites.

Discussed on #47141.
See also https://research.swtch.com/gomm for background.

Fixes #50860.

Change-Id: I0b178ee0c7747fa8985f8e48cd7b01063feb7dcc
Reviewed-on: https://go-review.googlesource.com/c/go/+/381317
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
Russ Cox 2022-01-26 16:56:00 -05:00
parent cf697253ab
commit ffe48e00ad
5 changed files with 1415 additions and 12 deletions

40
api/next/50860.txt Normal file
View File

@ -0,0 +1,40 @@
pkg sync/atomic, method (*Bool) CompareAndSwap(bool, bool) bool #50860
pkg sync/atomic, method (*Bool) Load() bool #50860
pkg sync/atomic, method (*Bool) Store(bool) #50860
pkg sync/atomic, method (*Bool) Swap(bool) bool #50860
pkg sync/atomic, method (*Int32) Add(int32) int32 #50860
pkg sync/atomic, method (*Int32) CompareAndSwap(int32, int32) bool #50860
pkg sync/atomic, method (*Int32) Load() int32 #50860
pkg sync/atomic, method (*Int32) Store(int32) #50860
pkg sync/atomic, method (*Int32) Swap(int32) int32 #50860
pkg sync/atomic, method (*Int64) Add(int64) int64 #50860
pkg sync/atomic, method (*Int64) CompareAndSwap(int64, int64) bool #50860
pkg sync/atomic, method (*Int64) Load() int64 #50860
pkg sync/atomic, method (*Int64) Store(int64) #50860
pkg sync/atomic, method (*Int64) Swap(int64) int64 #50860
pkg sync/atomic, method (*Pointer[$0]) CompareAndSwap(*$0, *$0) bool #50860
pkg sync/atomic, method (*Pointer[$0]) Load() *$0 #50860
pkg sync/atomic, method (*Pointer[$0]) Store(*$0) #50860
pkg sync/atomic, method (*Pointer[$0]) Swap(*$0) *$0 #50860
pkg sync/atomic, method (*Uint32) Add(uint32) uint32 #50860
pkg sync/atomic, method (*Uint32) CompareAndSwap(uint32, uint32) bool #50860
pkg sync/atomic, method (*Uint32) Load() uint32 #50860
pkg sync/atomic, method (*Uint32) Store(uint32) #50860
pkg sync/atomic, method (*Uint32) Swap(uint32) uint32 #50860
pkg sync/atomic, method (*Uint64) Add(uint64) uint64 #50860
pkg sync/atomic, method (*Uint64) CompareAndSwap(uint64, uint64) bool #50860
pkg sync/atomic, method (*Uint64) Load() uint64 #50860
pkg sync/atomic, method (*Uint64) Store(uint64) #50860
pkg sync/atomic, method (*Uint64) Swap(uint64) uint64 #50860
pkg sync/atomic, method (*Uintptr) Add(uintptr) uintptr #50860
pkg sync/atomic, method (*Uintptr) CompareAndSwap(uintptr, uintptr) bool #50860
pkg sync/atomic, method (*Uintptr) Load() uintptr #50860
pkg sync/atomic, method (*Uintptr) Store(uintptr) #50860
pkg sync/atomic, method (*Uintptr) Swap(uintptr) uintptr #50860
pkg sync/atomic, type Bool struct #50860
pkg sync/atomic, type Int32 struct #50860
pkg sync/atomic, type Int64 struct #50860
pkg sync/atomic, type Pointer[$0 interface{}] struct #50860
pkg sync/atomic, type Uint32 struct #50860
pkg sync/atomic, type Uint64 struct #50860
pkg sync/atomic, type Uintptr struct #50860

View File

@ -180,6 +180,42 @@ func TestIntendedInlining(t *testing.T) {
"net": {
"(*UDPConn).ReadFromUDP",
},
"sync/atomic": {
// (*Bool).CompareAndSwap handled below.
"(*Bool).Load",
"(*Bool).Store",
"(*Bool).Swap",
"(*Int32).Add",
"(*Int32).CompareAndSwap",
"(*Int32).Load",
"(*Int32).Store",
"(*Int32).Swap",
"(*Int64).Add",
"(*Int64).CompareAndSwap",
"(*Int64).Load",
"(*Int64).Store",
"(*Int64).Swap",
"(*Uint32).Add",
"(*Uint32).CompareAndSwap",
"(*Uint32).Load",
"(*Uint32).Store",
"(*Uint32).Swap",
"(*Uint64).Add",
"(*Uint64).CompareAndSwap",
"(*Uint64).Load",
"(*Uint64).Store",
"(*Uint64).Swap",
"(*Uintptr).Add",
"(*Uintptr).CompareAndSwap",
"(*Uintptr).Load",
"(*Uintptr).Store",
"(*Uintptr).Swap",
// TODO(rsc): Why are these not reported as inlined?
// "(*Pointer[T]).CompareAndSwap",
// "(*Pointer[T]).Load",
// "(*Pointer[T]).Store",
// "(*Pointer[T]).Swap",
},
}
if runtime.GOARCH != "386" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" {
@ -199,6 +235,8 @@ func TestIntendedInlining(t *testing.T) {
if bits.UintSize == 64 {
// mix is only defined on 64-bit architectures
want["runtime"] = append(want["runtime"], "mix")
// (*Bool).CompareAndSwap is just over budget on 32-bit systems (386, arm).
want["sync/atomic"] = append(want["sync/atomic"], "(*Bool).CompareAndSwap")
}
switch runtime.GOARCH {

View File

@ -167,6 +167,11 @@ func calcStructOffset(errtype *Type, t *Type, o int64, flag int) int64 {
if maxalign < 1 {
maxalign = 1
}
// Special case: sync/atomic.align64 is an empty struct we recognize
// as a signal that the struct it contains must be 64-bit-aligned.
if isStruct && t.NumFields() == 0 && t.Sym() != nil && t.Sym().Name == "align64" && isSyncAtomic(t.Sym().Pkg) {
maxalign = 8
}
lastzero := int64(0)
for _, f := range t.Fields().Slice() {
if f.Type == nil {
@ -226,6 +231,10 @@ func calcStructOffset(errtype *Type, t *Type, o int64, flag int) int64 {
return o
}
func isSyncAtomic(p *Pkg) bool {
return p.Prefix == "sync/atomic" || p.Prefix == `""` && base.Ctxt.Pkgpath == "sync/atomic"
}
// CalcSize calculates and stores the size and alignment for t.
// If CalcSizeDisabled is set, and the size/alignment
// have not already been calculated, it calls Fatal.

File diff suppressed because it is too large Load Diff

191
src/sync/atomic/type.go Normal file
View File

@ -0,0 +1,191 @@
// Copyright 2022 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 atomic
import "unsafe"
// A Bool is an atomic boolean value.
// The zero value is false.
type Bool struct {
_ noCopy
v uint32
}
// Load atomically loads and returns the value stored in x.
func (x *Bool) Load() bool { return LoadUint32(&x.v) != 0 }
// Store atomically stores val into x.
func (x *Bool) Store(val bool) { StoreUint32(&x.v, b32(val)) }
// Swap atomically stores new into x and returns the previous value.
func (x *Bool) Swap(new bool) (old bool) { return SwapUint32(&x.v, b32(new)) != 0 }
// CompareAndSwap executes the compare-and-swap operation for the boolean value x.
func (x *Bool) CompareAndSwap(old, new bool) (swapped bool) {
return CompareAndSwapUint32(&x.v, b32(old), b32(new))
}
// b32 returns a uint32 0 or 1 representing b.
func b32(b bool) uint32 {
if b {
return 1
}
return 0
}
// A Pointer is an atomic pointer of type *T. The zero value is a nil *T.
type Pointer[T any] struct {
_ noCopy
v unsafe.Pointer
}
// Load atomically loads and returns the value stored in x.
func (x *Pointer[T]) Load() *T { return (*T)(LoadPointer(&x.v)) }
// Store atomically stores val into x.
func (x *Pointer[T]) Store(val *T) { StorePointer(&x.v, unsafe.Pointer(val)) }
// Swap atomically stores new into x and returns the previous value.
func (x *Pointer[T]) Swap(new *T) (old *T) { return (*T)(SwapPointer(&x.v, unsafe.Pointer(new))) }
// CompareAndSwap executes the compare-and-swap operation for x.
func (x *Pointer[T]) CompareAndSwap(old, new *T) (swapped bool) {
return CompareAndSwapPointer(&x.v, unsafe.Pointer(old), unsafe.Pointer(new))
}
// An Int32 is an atomic int32. The zero value is zero.
type Int32 struct {
_ noCopy
v int32
}
// Load atomically loads and returns the value stored in x.
func (x *Int32) Load() int32 { return LoadInt32(&x.v) }
// Store atomically stores val into x.
func (x *Int32) Store(val int32) { StoreInt32(&x.v, val) }
// Swap atomically stores new into x and returns the previous value.
func (x *Int32) Swap(new int32) (old int32) { return SwapInt32(&x.v, new) }
// CompareAndSwap executes the compare-and-swap operation for x.
func (x *Int32) CompareAndSwap(old, new int32) (swapped bool) {
return CompareAndSwapInt32(&x.v, old, new)
}
// Add atomically adds delta to x and returns the new value.
func (x *Int32) Add(delta int32) (new int32) { return AddInt32(&x.v, delta) }
// An Int64 is an atomic int64. The zero value is zero.
type Int64 struct {
_ noCopy
_ align64
v int64
}
// Load atomically loads and returns the value stored in x.
func (x *Int64) Load() int64 { return LoadInt64(&x.v) }
// Store atomically stores val into x.
func (x *Int64) Store(val int64) { StoreInt64(&x.v, val) }
// Swap atomically stores new into x and returns the previous value.
func (x *Int64) Swap(new int64) (old int64) { return SwapInt64(&x.v, new) }
// CompareAndSwap executes the compare-and-swap operation for x.
func (x *Int64) CompareAndSwap(old, new int64) (swapped bool) {
return CompareAndSwapInt64(&x.v, old, new)
}
// Add atomically adds delta to x and returns the new value.
func (x *Int64) Add(delta int64) (new int64) { return AddInt64(&x.v, delta) }
// An Uint32 is an atomic uint32. The zero value is zero.
type Uint32 struct {
_ noCopy
v uint32
}
// Load atomically loads and returns the value stored in x.
func (x *Uint32) Load() uint32 { return LoadUint32(&x.v) }
// Store atomically stores val into x.
func (x *Uint32) Store(val uint32) { StoreUint32(&x.v, val) }
// Swap atomically stores new into x and returns the previous value.
func (x *Uint32) Swap(new uint32) (old uint32) { return SwapUint32(&x.v, new) }
// CompareAndSwap executes the compare-and-swap operation for x.
func (x *Uint32) CompareAndSwap(old, new uint32) (swapped bool) {
return CompareAndSwapUint32(&x.v, old, new)
}
// Add atomically adds delta to x and returns the new value.
func (x *Uint32) Add(delta uint32) (new uint32) { return AddUint32(&x.v, delta) }
// An Uint64 is an atomic uint64. The zero value is zero.
type Uint64 struct {
_ noCopy
_ align64
v uint64
}
// Load atomically loads and returns the value stored in x.
func (x *Uint64) Load() uint64 { return LoadUint64(&x.v) }
// Store atomically stores val into x.
func (x *Uint64) Store(val uint64) { StoreUint64(&x.v, val) }
// Swap atomically stores new into x and returns the previous value.
func (x *Uint64) Swap(new uint64) (old uint64) { return SwapUint64(&x.v, new) }
// CompareAndSwap executes the compare-and-swap operation for x.
func (x *Uint64) CompareAndSwap(old, new uint64) (swapped bool) {
return CompareAndSwapUint64(&x.v, old, new)
}
// Add atomically adds delta to x and returns the new value.
func (x *Uint64) Add(delta uint64) (new uint64) { return AddUint64(&x.v, delta) }
// An Uintptr is an atomic uintptr. The zero value is zero.
type Uintptr struct {
_ noCopy
v uintptr
}
// Load atomically loads and returns the value stored in x.
func (x *Uintptr) Load() uintptr { return LoadUintptr(&x.v) }
// Store atomically stores val into x.
func (x *Uintptr) Store(val uintptr) { StoreUintptr(&x.v, val) }
// Swap atomically stores new into x and returns the previous value.
func (x *Uintptr) Swap(new uintptr) (old uintptr) { return SwapUintptr(&x.v, new) }
// CompareAndSwap executes the compare-and-swap operation for x.
func (x *Uintptr) CompareAndSwap(old, new uintptr) (swapped bool) {
return CompareAndSwapUintptr(&x.v, old, new)
}
// Add atomically adds delta to x and returns the new value.
func (x *Uintptr) Add(delta uintptr) (new uintptr) { return AddUintptr(&x.v, delta) }
// noCopy may be added to structs which must not be copied
// after the first use.
//
// See https://golang.org/issues/8005#issuecomment-190753527
// for details.
//
// Note that it must not be embedded, due to the Lock and Unlock methods.
type noCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
// align64 may be added to structs that must be 64-bit aligned.
// This struct is recognized by a special case in the compiler
// and will not work if copied to any other package.
type align64 struct{}