aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/sync
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2021-07-30 14:28:58 -0700
committerIan Lance Taylor <iant@golang.org>2021-08-12 20:23:07 -0700
commitc5b21c3f4c17b0649155035d2f9aa97b2da8a813 (patch)
treec6d3a68b503ba5b16182acbb958e3e5dbc95a43b /libgo/go/sync
parent72be20e20299ec57b4bc9ba03d5b7d6bf10e97cc (diff)
downloadgcc-c5b21c3f4c17b0649155035d2f9aa97b2da8a813.zip
gcc-c5b21c3f4c17b0649155035d2f9aa97b2da8a813.tar.gz
gcc-c5b21c3f4c17b0649155035d2f9aa97b2da8a813.tar.bz2
libgo: update to Go1.17rc2
Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/341629
Diffstat (limited to 'libgo/go/sync')
-rw-r--r--libgo/go/sync/atomic/value.go129
-rw-r--r--libgo/go/sync/atomic/value_test.go139
-rw-r--r--libgo/go/sync/map.go3
-rw-r--r--libgo/go/sync/pool_test.go1
-rw-r--r--libgo/go/sync/runtime2.go1
-rw-r--r--libgo/go/sync/runtime2_lockrank.go1
6 files changed, 262 insertions, 12 deletions
diff --git a/libgo/go/sync/atomic/value.go b/libgo/go/sync/atomic/value.go
index eab7e70..61f81d8 100644
--- a/libgo/go/sync/atomic/value.go
+++ b/libgo/go/sync/atomic/value.go
@@ -25,7 +25,7 @@ type ifaceWords struct {
// Load returns the value set by the most recent Store.
// It returns nil if there has been no call to Store for this Value.
-func (v *Value) Load() (x interface{}) {
+func (v *Value) Load() (val interface{}) {
vp := (*ifaceWords)(unsafe.Pointer(v))
typ := LoadPointer(&vp.typ)
if typ == nil || uintptr(typ) == ^uintptr(0) {
@@ -33,21 +33,21 @@ func (v *Value) Load() (x interface{}) {
return nil
}
data := LoadPointer(&vp.data)
- xp := (*ifaceWords)(unsafe.Pointer(&x))
- xp.typ = typ
- xp.data = data
+ vlp := (*ifaceWords)(unsafe.Pointer(&val))
+ vlp.typ = typ
+ vlp.data = data
return
}
// Store sets the value of the Value to x.
// All calls to Store for a given Value must use values of the same concrete type.
// Store of an inconsistent type panics, as does Store(nil).
-func (v *Value) Store(x interface{}) {
- if x == nil {
+func (v *Value) Store(val interface{}) {
+ if val == nil {
panic("sync/atomic: store of nil value into Value")
}
vp := (*ifaceWords)(unsafe.Pointer(v))
- xp := (*ifaceWords)(unsafe.Pointer(&x))
+ vlp := (*ifaceWords)(unsafe.Pointer(&val))
for {
typ := LoadPointer(&vp.typ)
if typ == nil {
@@ -61,8 +61,8 @@ func (v *Value) Store(x interface{}) {
continue
}
// Complete first store.
- StorePointer(&vp.data, xp.data)
- StorePointer(&vp.typ, xp.typ)
+ StorePointer(&vp.data, vlp.data)
+ StorePointer(&vp.typ, vlp.typ)
runtime_procUnpin()
return
}
@@ -73,14 +73,121 @@ func (v *Value) Store(x interface{}) {
continue
}
// First store completed. Check type and overwrite data.
- if typ != xp.typ {
+ if typ != vlp.typ {
panic("sync/atomic: store of inconsistently typed value into Value")
}
- StorePointer(&vp.data, xp.data)
+ StorePointer(&vp.data, vlp.data)
return
}
}
+// Swap stores new into Value and returns the previous value. It returns nil if
+// the Value is empty.
+//
+// All calls to Swap for a given Value must use values of the same concrete
+// type. Swap of an inconsistent type panics, as does Swap(nil).
+func (v *Value) Swap(new interface{}) (old interface{}) {
+ if new == nil {
+ panic("sync/atomic: swap of nil value into Value")
+ }
+ vp := (*ifaceWords)(unsafe.Pointer(v))
+ np := (*ifaceWords)(unsafe.Pointer(&new))
+ for {
+ typ := LoadPointer(&vp.typ)
+ if typ == nil {
+ // Attempt to start first store.
+ // Disable preemption so that other goroutines can use
+ // active spin wait to wait for completion; and so that
+ // GC does not see the fake type accidentally.
+ runtime_procPin()
+ if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
+ runtime_procUnpin()
+ continue
+ }
+ // Complete first store.
+ StorePointer(&vp.data, np.data)
+ StorePointer(&vp.typ, np.typ)
+ runtime_procUnpin()
+ return nil
+ }
+ if uintptr(typ) == ^uintptr(0) {
+ // First store in progress. Wait.
+ // Since we disable preemption around the first store,
+ // we can wait with active spinning.
+ continue
+ }
+ // First store completed. Check type and overwrite data.
+ if typ != np.typ {
+ panic("sync/atomic: swap of inconsistently typed value into Value")
+ }
+ op := (*ifaceWords)(unsafe.Pointer(&old))
+ op.typ, op.data = np.typ, SwapPointer(&vp.data, np.data)
+ return old
+ }
+}
+
+// CompareAndSwapPointer executes the compare-and-swap operation for the Value.
+//
+// All calls to CompareAndSwap for a given Value must use values of the same
+// concrete type. CompareAndSwap of an inconsistent type panics, as does
+// CompareAndSwap(old, nil).
+func (v *Value) CompareAndSwap(old, new interface{}) (swapped bool) {
+ if new == nil {
+ panic("sync/atomic: compare and swap of nil value into Value")
+ }
+ vp := (*ifaceWords)(unsafe.Pointer(v))
+ np := (*ifaceWords)(unsafe.Pointer(&new))
+ op := (*ifaceWords)(unsafe.Pointer(&old))
+ if op.typ != nil && np.typ != op.typ {
+ panic("sync/atomic: compare and swap of inconsistently typed values")
+ }
+ for {
+ typ := LoadPointer(&vp.typ)
+ if typ == nil {
+ if old != nil {
+ return false
+ }
+ // Attempt to start first store.
+ // Disable preemption so that other goroutines can use
+ // active spin wait to wait for completion; and so that
+ // GC does not see the fake type accidentally.
+ runtime_procPin()
+ if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
+ runtime_procUnpin()
+ continue
+ }
+ // Complete first store.
+ StorePointer(&vp.data, np.data)
+ StorePointer(&vp.typ, np.typ)
+ runtime_procUnpin()
+ return true
+ }
+ if uintptr(typ) == ^uintptr(0) {
+ // First store in progress. Wait.
+ // Since we disable preemption around the first store,
+ // we can wait with active spinning.
+ continue
+ }
+ // First store completed. Check type and overwrite data.
+ if typ != np.typ {
+ panic("sync/atomic: compare and swap of inconsistently typed value into Value")
+ }
+ // Compare old and current via runtime equality check.
+ // This allows value types to be compared, something
+ // not offered by the package functions.
+ // CompareAndSwapPointer below only ensures vp.data
+ // has not changed since LoadPointer.
+ data := LoadPointer(&vp.data)
+ var i interface{}
+ (*ifaceWords)(unsafe.Pointer(&i)).typ = typ
+ (*ifaceWords)(unsafe.Pointer(&i)).data = data
+ if i != old {
+ return false
+ }
+ return CompareAndSwapPointer(&vp.data, data, np.data)
+ }
+}
+
// Disable/enable preemption, implemented in runtime.
func runtime_procPin()
func runtime_procUnpin()
diff --git a/libgo/go/sync/atomic/value_test.go b/libgo/go/sync/atomic/value_test.go
index f289766..a5e717d 100644
--- a/libgo/go/sync/atomic/value_test.go
+++ b/libgo/go/sync/atomic/value_test.go
@@ -7,6 +7,9 @@ package atomic_test
import (
"math/rand"
"runtime"
+ "strconv"
+ "sync"
+ "sync/atomic"
. "sync/atomic"
"testing"
)
@@ -133,3 +136,139 @@ func BenchmarkValueRead(b *testing.B) {
}
})
}
+
+var Value_SwapTests = []struct {
+ init interface{}
+ new interface{}
+ want interface{}
+ err interface{}
+}{
+ {init: nil, new: nil, err: "sync/atomic: swap of nil value into Value"},
+ {init: nil, new: true, want: nil, err: nil},
+ {init: true, new: "", err: "sync/atomic: swap of inconsistently typed value into Value"},
+ {init: true, new: false, want: true, err: nil},
+}
+
+func TestValue_Swap(t *testing.T) {
+ for i, tt := range Value_SwapTests {
+ t.Run(strconv.Itoa(i), func(t *testing.T) {
+ var v Value
+ if tt.init != nil {
+ v.Store(tt.init)
+ }
+ defer func() {
+ err := recover()
+ switch {
+ case tt.err == nil && err != nil:
+ t.Errorf("should not panic, got %v", err)
+ case tt.err != nil && err == nil:
+ t.Errorf("should panic %v, got <nil>", tt.err)
+ }
+ }()
+ if got := v.Swap(tt.new); got != tt.want {
+ t.Errorf("got %v, want %v", got, tt.want)
+ }
+ if got := v.Load(); got != tt.new {
+ t.Errorf("got %v, want %v", got, tt.new)
+ }
+ })
+ }
+}
+
+func TestValueSwapConcurrent(t *testing.T) {
+ var v Value
+ var count uint64
+ var g sync.WaitGroup
+ var m, n uint64 = 10000, 10000
+ if testing.Short() {
+ m = 1000
+ n = 1000
+ }
+ for i := uint64(0); i < m*n; i += n {
+ i := i
+ g.Add(1)
+ go func() {
+ var c uint64
+ for new := i; new < i+n; new++ {
+ if old := v.Swap(new); old != nil {
+ c += old.(uint64)
+ }
+ }
+ atomic.AddUint64(&count, c)
+ g.Done()
+ }()
+ }
+ g.Wait()
+ if want, got := (m*n-1)*(m*n)/2, count+v.Load().(uint64); got != want {
+ t.Errorf("sum from 0 to %d was %d, want %v", m*n-1, got, want)
+ }
+}
+
+var heapA, heapB = struct{ uint }{0}, struct{ uint }{0}
+
+var Value_CompareAndSwapTests = []struct {
+ init interface{}
+ new interface{}
+ old interface{}
+ want bool
+ err interface{}
+}{
+ {init: nil, new: nil, old: nil, err: "sync/atomic: compare and swap of nil value into Value"},
+ {init: nil, new: true, old: "", err: "sync/atomic: compare and swap of inconsistently typed values into Value"},
+ {init: nil, new: true, old: true, want: false, err: nil},
+ {init: nil, new: true, old: nil, want: true, err: nil},
+ {init: true, new: "", err: "sync/atomic: compare and swap of inconsistently typed value into Value"},
+ {init: true, new: true, old: false, want: false, err: nil},
+ {init: true, new: true, old: true, want: true, err: nil},
+ {init: heapA, new: struct{ uint }{1}, old: heapB, want: true, err: nil},
+}
+
+func TestValue_CompareAndSwap(t *testing.T) {
+ for i, tt := range Value_CompareAndSwapTests {
+ t.Run(strconv.Itoa(i), func(t *testing.T) {
+ var v Value
+ if tt.init != nil {
+ v.Store(tt.init)
+ }
+ defer func() {
+ err := recover()
+ switch {
+ case tt.err == nil && err != nil:
+ t.Errorf("got %v, wanted no panic", err)
+ case tt.err != nil && err == nil:
+ t.Errorf("did not panic, want %v", tt.err)
+ }
+ }()
+ if got := v.CompareAndSwap(tt.old, tt.new); got != tt.want {
+ t.Errorf("got %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestValueCompareAndSwapConcurrent(t *testing.T) {
+ var v Value
+ var w sync.WaitGroup
+ v.Store(0)
+ m, n := 1000, 100
+ if testing.Short() {
+ m = 100
+ n = 100
+ }
+ for i := 0; i < m; i++ {
+ i := i
+ w.Add(1)
+ go func() {
+ for j := i; j < m*n; runtime.Gosched() {
+ if v.CompareAndSwap(j, j+1) {
+ j += m
+ }
+ }
+ w.Done()
+ }()
+ }
+ w.Wait()
+ if stop := v.Load().(int); stop != m*n {
+ t.Errorf("did not get to %v, stopped at %v", m*n, stop)
+ }
+}
diff --git a/libgo/go/sync/map.go b/libgo/go/sync/map.go
index 9ad2535..dfb62dd 100644
--- a/libgo/go/sync/map.go
+++ b/libgo/go/sync/map.go
@@ -73,7 +73,8 @@ var expunged = unsafe.Pointer(new(interface{}))
type entry struct {
// p points to the interface{} value stored for the entry.
//
- // If p == nil, the entry has been deleted and m.dirty == nil.
+ // If p == nil, the entry has been deleted, and either m.dirty == nil or
+ // m.dirty[key] is e.
//
// If p == expunged, the entry has been deleted, m.dirty != nil, and the entry
// is missing from m.dirty.
diff --git a/libgo/go/sync/pool_test.go b/libgo/go/sync/pool_test.go
index c67bd8c..9371644 100644
--- a/libgo/go/sync/pool_test.go
+++ b/libgo/go/sync/pool_test.go
@@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.
// Pool is no-op under race detector, so all these tests do not work.
+//go:build !race
// +build !race
package sync_test
diff --git a/libgo/go/sync/runtime2.go b/libgo/go/sync/runtime2.go
index f10c4e8..c4b4489 100644
--- a/libgo/go/sync/runtime2.go
+++ b/libgo/go/sync/runtime2.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !goexperiment.staticlockranking
// +build !goexperiment.staticlockranking
package sync
diff --git a/libgo/go/sync/runtime2_lockrank.go b/libgo/go/sync/runtime2_lockrank.go
index aaa1c27..e91fdb6 100644
--- a/libgo/go/sync/runtime2_lockrank.go
+++ b/libgo/go/sync/runtime2_lockrank.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build goexperiment.staticlockranking
// +build goexperiment.staticlockranking
package sync