Skip to content

Commit

Permalink
sync/atomic: add typed atomic values
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
rsc committed May 4, 2022
1 parent cf69725 commit ffe48e0
Show file tree
Hide file tree
Showing 5 changed files with 1,415 additions and 12 deletions.
40 changes: 40 additions & 0 deletions api/next/50860.txt
Original file line number Diff line number Diff line change
@@ -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
38 changes: 38 additions & 0 deletions src/cmd/compile/internal/test/inl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand All @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions src/cmd/compile/internal/types/size.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down
Loading

0 comments on commit ffe48e0

Please sign in to comment.