mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
The function LoweredAtomicCas32 is implemented using the LL-SC instruction pair on loong64, mips64x, riscv64. However,the LL instruction on loong64, mips64x, riscv64 is sign-extended, so it is necessary to sign-extend the 2nd parameter "old" of the LoweredAtomicCas32, so that the instruction BNE after LL can get the desired result. The function prototype of LoweredAtomicCas32 in golang: func Cas32(ptr *uint32, old, new uint32) bool When using an intrinsify implementation: case 1: (*ptr) <= 0x80000000 && old < 0x80000000 E.g: (*ptr) = 0x7FFFFFFF, old = Rarg1= 0x7FFFFFFF After run the instruction "LL (Rarg0), Rtmp": Rtmp = 0x7FFFFFFF Rtmp ! = Rarg1(old) is false, the result we expect case 2: (*ptr) >= 0x80000000 && old >= 0x80000000 E.g: (*ptr) = 0x80000000, old = Rarg1= 0x80000000 After run the instruction "LL (Rarg0), Rtmp": Rtmp = 0xFFFFFFFF_80000000 Rtmp ! = Rarg1(old) is true, which we do not expect When using an non-intrinsify implementation: Because Rarg1 is loaded from the stack using sign-extended instructions ld.w, the situation described in Case 2 above does not occur Benchmarks on linux/loong64: name old time/op new time/op delta Cas 50.0ns ± 0% 50.1ns ± 0% ~ (p=1.000 n=1+1) Cas64 50.0ns ± 0% 50.1ns ± 0% ~ (p=1.000 n=1+1) Cas-4 56.0ns ± 0% 56.0ns ± 0% ~ (p=1.000 n=1+1) Cas64-4 56.0ns ± 0% 56.0ns ± 0% ~ (p=1.000 n=1+1) Benchmarks on Loongson 3A4000 (GOARCH=mips64le, 1.8GHz) name old time/op new time/op delta Cas 70.4ns ± 0% 70.3ns ± 0% ~ (p=1.000 n=1+1) Cas64 70.7ns ± 0% 70.6ns ± 0% ~ (p=1.000 n=1+1) Cas-4 81.1ns ± 0% 80.8ns ± 0% ~ (p=1.000 n=1+1) Cas64-4 80.9ns ± 0% 80.9ns ± 0% ~ (p=1.000 n=1+1) Fixes #57344 Change-Id: I190a7fc648023b15fa392f7fdda5ac18c1561bac Reviewed-on: https://go-review.googlesource.com/c/go/+/457135 Run-TryBot: Than McIntosh <thanm@google.com> Reviewed-by: Cherry Mui <cherryyz@google.com> Reviewed-by: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Wayne Zuo <wdvxdr@golangcn.org> Reviewed-by: Than McIntosh <thanm@google.com> Reviewed-by: David Chase <drchase@google.com> Reviewed-on: https://go-review.googlesource.com/c/go/+/458357 Run-TryBot: David Chase <drchase@google.com> Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
387 lines
8.5 KiB
Go
387 lines
8.5 KiB
Go
// Copyright 2015 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_test
|
|
|
|
import (
|
|
"internal/goarch"
|
|
"runtime"
|
|
"runtime/internal/atomic"
|
|
"testing"
|
|
"unsafe"
|
|
)
|
|
|
|
func runParallel(N, iter int, f func()) {
|
|
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(int(N)))
|
|
done := make(chan bool)
|
|
for i := 0; i < N; i++ {
|
|
go func() {
|
|
for j := 0; j < iter; j++ {
|
|
f()
|
|
}
|
|
done <- true
|
|
}()
|
|
}
|
|
for i := 0; i < N; i++ {
|
|
<-done
|
|
}
|
|
}
|
|
|
|
func TestXadduintptr(t *testing.T) {
|
|
N := 20
|
|
iter := 100000
|
|
if testing.Short() {
|
|
N = 10
|
|
iter = 10000
|
|
}
|
|
inc := uintptr(100)
|
|
total := uintptr(0)
|
|
runParallel(N, iter, func() {
|
|
atomic.Xadduintptr(&total, inc)
|
|
})
|
|
if want := uintptr(N*iter) * inc; want != total {
|
|
t.Fatalf("xadduintpr error, want %d, got %d", want, total)
|
|
}
|
|
total = 0
|
|
runParallel(N, iter, func() {
|
|
atomic.Xadduintptr(&total, inc)
|
|
atomic.Xadduintptr(&total, uintptr(-int64(inc)))
|
|
})
|
|
if total != 0 {
|
|
t.Fatalf("xadduintpr total error, want %d, got %d", 0, total)
|
|
}
|
|
}
|
|
|
|
// Tests that xadduintptr correctly updates 64-bit values. The place where
|
|
// we actually do so is mstats.go, functions mSysStat{Inc,Dec}.
|
|
func TestXadduintptrOnUint64(t *testing.T) {
|
|
if goarch.BigEndian {
|
|
// On big endian architectures, we never use xadduintptr to update
|
|
// 64-bit values and hence we skip the test. (Note that functions
|
|
// mSysStat{Inc,Dec} in mstats.go have explicit checks for
|
|
// big-endianness.)
|
|
t.Skip("skip xadduintptr on big endian architecture")
|
|
}
|
|
const inc = 100
|
|
val := uint64(0)
|
|
atomic.Xadduintptr((*uintptr)(unsafe.Pointer(&val)), inc)
|
|
if inc != val {
|
|
t.Fatalf("xadduintptr should increase lower-order bits, want %d, got %d", inc, val)
|
|
}
|
|
}
|
|
|
|
func shouldPanic(t *testing.T, name string, f func()) {
|
|
defer func() {
|
|
// Check that all GC maps are sane.
|
|
runtime.GC()
|
|
|
|
err := recover()
|
|
want := "unaligned 64-bit atomic operation"
|
|
if err == nil {
|
|
t.Errorf("%s did not panic", name)
|
|
} else if s, _ := err.(string); s != want {
|
|
t.Errorf("%s: wanted panic %q, got %q", name, want, err)
|
|
}
|
|
}()
|
|
f()
|
|
}
|
|
|
|
// Variant of sync/atomic's TestUnaligned64:
|
|
func TestUnaligned64(t *testing.T) {
|
|
// Unaligned 64-bit atomics on 32-bit systems are
|
|
// a continual source of pain. Test that on 32-bit systems they crash
|
|
// instead of failing silently.
|
|
|
|
if unsafe.Sizeof(int(0)) != 4 {
|
|
t.Skip("test only runs on 32-bit systems")
|
|
}
|
|
|
|
x := make([]uint32, 4)
|
|
u := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) | 4) // force alignment to 4
|
|
|
|
up64 := (*uint64)(u) // misaligned
|
|
p64 := (*int64)(u) // misaligned
|
|
|
|
shouldPanic(t, "Load64", func() { atomic.Load64(up64) })
|
|
shouldPanic(t, "Loadint64", func() { atomic.Loadint64(p64) })
|
|
shouldPanic(t, "Store64", func() { atomic.Store64(up64, 0) })
|
|
shouldPanic(t, "Xadd64", func() { atomic.Xadd64(up64, 1) })
|
|
shouldPanic(t, "Xchg64", func() { atomic.Xchg64(up64, 1) })
|
|
shouldPanic(t, "Cas64", func() { atomic.Cas64(up64, 1, 2) })
|
|
}
|
|
|
|
func TestAnd8(t *testing.T) {
|
|
// Basic sanity check.
|
|
x := uint8(0xff)
|
|
for i := uint8(0); i < 8; i++ {
|
|
atomic.And8(&x, ^(1 << i))
|
|
if r := uint8(0xff) << (i + 1); x != r {
|
|
t.Fatalf("clearing bit %#x: want %#x, got %#x", uint8(1<<i), r, x)
|
|
}
|
|
}
|
|
|
|
// Set every bit in array to 1.
|
|
a := make([]uint8, 1<<12)
|
|
for i := range a {
|
|
a[i] = 0xff
|
|
}
|
|
|
|
// Clear array bit-by-bit in different goroutines.
|
|
done := make(chan bool)
|
|
for i := 0; i < 8; i++ {
|
|
m := ^uint8(1 << i)
|
|
go func() {
|
|
for i := range a {
|
|
atomic.And8(&a[i], m)
|
|
}
|
|
done <- true
|
|
}()
|
|
}
|
|
for i := 0; i < 8; i++ {
|
|
<-done
|
|
}
|
|
|
|
// Check that the array has been totally cleared.
|
|
for i, v := range a {
|
|
if v != 0 {
|
|
t.Fatalf("a[%v] not cleared: want %#x, got %#x", i, uint8(0), v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAnd(t *testing.T) {
|
|
// Basic sanity check.
|
|
x := uint32(0xffffffff)
|
|
for i := uint32(0); i < 32; i++ {
|
|
atomic.And(&x, ^(1 << i))
|
|
if r := uint32(0xffffffff) << (i + 1); x != r {
|
|
t.Fatalf("clearing bit %#x: want %#x, got %#x", uint32(1<<i), r, x)
|
|
}
|
|
}
|
|
|
|
// Set every bit in array to 1.
|
|
a := make([]uint32, 1<<12)
|
|
for i := range a {
|
|
a[i] = 0xffffffff
|
|
}
|
|
|
|
// Clear array bit-by-bit in different goroutines.
|
|
done := make(chan bool)
|
|
for i := 0; i < 32; i++ {
|
|
m := ^uint32(1 << i)
|
|
go func() {
|
|
for i := range a {
|
|
atomic.And(&a[i], m)
|
|
}
|
|
done <- true
|
|
}()
|
|
}
|
|
for i := 0; i < 32; i++ {
|
|
<-done
|
|
}
|
|
|
|
// Check that the array has been totally cleared.
|
|
for i, v := range a {
|
|
if v != 0 {
|
|
t.Fatalf("a[%v] not cleared: want %#x, got %#x", i, uint32(0), v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOr8(t *testing.T) {
|
|
// Basic sanity check.
|
|
x := uint8(0)
|
|
for i := uint8(0); i < 8; i++ {
|
|
atomic.Or8(&x, 1<<i)
|
|
if r := (uint8(1) << (i + 1)) - 1; x != r {
|
|
t.Fatalf("setting bit %#x: want %#x, got %#x", uint8(1)<<i, r, x)
|
|
}
|
|
}
|
|
|
|
// Start with every bit in array set to 0.
|
|
a := make([]uint8, 1<<12)
|
|
|
|
// Set every bit in array bit-by-bit in different goroutines.
|
|
done := make(chan bool)
|
|
for i := 0; i < 8; i++ {
|
|
m := uint8(1 << i)
|
|
go func() {
|
|
for i := range a {
|
|
atomic.Or8(&a[i], m)
|
|
}
|
|
done <- true
|
|
}()
|
|
}
|
|
for i := 0; i < 8; i++ {
|
|
<-done
|
|
}
|
|
|
|
// Check that the array has been totally set.
|
|
for i, v := range a {
|
|
if v != 0xff {
|
|
t.Fatalf("a[%v] not fully set: want %#x, got %#x", i, uint8(0xff), v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOr(t *testing.T) {
|
|
// Basic sanity check.
|
|
x := uint32(0)
|
|
for i := uint32(0); i < 32; i++ {
|
|
atomic.Or(&x, 1<<i)
|
|
if r := (uint32(1) << (i + 1)) - 1; x != r {
|
|
t.Fatalf("setting bit %#x: want %#x, got %#x", uint32(1)<<i, r, x)
|
|
}
|
|
}
|
|
|
|
// Start with every bit in array set to 0.
|
|
a := make([]uint32, 1<<12)
|
|
|
|
// Set every bit in array bit-by-bit in different goroutines.
|
|
done := make(chan bool)
|
|
for i := 0; i < 32; i++ {
|
|
m := uint32(1 << i)
|
|
go func() {
|
|
for i := range a {
|
|
atomic.Or(&a[i], m)
|
|
}
|
|
done <- true
|
|
}()
|
|
}
|
|
for i := 0; i < 32; i++ {
|
|
<-done
|
|
}
|
|
|
|
// Check that the array has been totally set.
|
|
for i, v := range a {
|
|
if v != 0xffffffff {
|
|
t.Fatalf("a[%v] not fully set: want %#x, got %#x", i, uint32(0xffffffff), v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBitwiseContended8(t *testing.T) {
|
|
// Start with every bit in array set to 0.
|
|
a := make([]uint8, 16)
|
|
|
|
// Iterations to try.
|
|
N := 1 << 16
|
|
if testing.Short() {
|
|
N = 1 << 10
|
|
}
|
|
|
|
// Set and then clear every bit in the array bit-by-bit in different goroutines.
|
|
done := make(chan bool)
|
|
for i := 0; i < 8; i++ {
|
|
m := uint8(1 << i)
|
|
go func() {
|
|
for n := 0; n < N; n++ {
|
|
for i := range a {
|
|
atomic.Or8(&a[i], m)
|
|
if atomic.Load8(&a[i])&m != m {
|
|
t.Errorf("a[%v] bit %#x not set", i, m)
|
|
}
|
|
atomic.And8(&a[i], ^m)
|
|
if atomic.Load8(&a[i])&m != 0 {
|
|
t.Errorf("a[%v] bit %#x not clear", i, m)
|
|
}
|
|
}
|
|
}
|
|
done <- true
|
|
}()
|
|
}
|
|
for i := 0; i < 8; i++ {
|
|
<-done
|
|
}
|
|
|
|
// Check that the array has been totally cleared.
|
|
for i, v := range a {
|
|
if v != 0 {
|
|
t.Fatalf("a[%v] not cleared: want %#x, got %#x", i, uint8(0), v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBitwiseContended(t *testing.T) {
|
|
// Start with every bit in array set to 0.
|
|
a := make([]uint32, 16)
|
|
|
|
// Iterations to try.
|
|
N := 1 << 16
|
|
if testing.Short() {
|
|
N = 1 << 10
|
|
}
|
|
|
|
// Set and then clear every bit in the array bit-by-bit in different goroutines.
|
|
done := make(chan bool)
|
|
for i := 0; i < 32; i++ {
|
|
m := uint32(1 << i)
|
|
go func() {
|
|
for n := 0; n < N; n++ {
|
|
for i := range a {
|
|
atomic.Or(&a[i], m)
|
|
if atomic.Load(&a[i])&m != m {
|
|
t.Errorf("a[%v] bit %#x not set", i, m)
|
|
}
|
|
atomic.And(&a[i], ^m)
|
|
if atomic.Load(&a[i])&m != 0 {
|
|
t.Errorf("a[%v] bit %#x not clear", i, m)
|
|
}
|
|
}
|
|
}
|
|
done <- true
|
|
}()
|
|
}
|
|
for i := 0; i < 32; i++ {
|
|
<-done
|
|
}
|
|
|
|
// Check that the array has been totally cleared.
|
|
for i, v := range a {
|
|
if v != 0 {
|
|
t.Fatalf("a[%v] not cleared: want %#x, got %#x", i, uint32(0), v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCasRel(t *testing.T) {
|
|
const _magic = 0x5a5aa5a5
|
|
var x struct {
|
|
before uint32
|
|
i uint32
|
|
after uint32
|
|
o uint32
|
|
n uint32
|
|
}
|
|
|
|
x.before = _magic
|
|
x.after = _magic
|
|
for j := 0; j < 32; j += 1 {
|
|
x.i = (1 << j) + 0
|
|
x.o = (1 << j) + 0
|
|
x.n = (1 << j) + 1
|
|
if !atomic.CasRel(&x.i, x.o, x.n) {
|
|
t.Fatalf("should have swapped %#x %#x", x.o, x.n)
|
|
}
|
|
|
|
if x.i != x.n {
|
|
t.Fatalf("wrong x.i after swap: x.i=%#x x.n=%#x", x.i, x.n)
|
|
}
|
|
|
|
if x.before != _magic || x.after != _magic {
|
|
t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, _magic, _magic)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStorepNoWB(t *testing.T) {
|
|
var p [2]*int
|
|
for i := range p {
|
|
atomic.StorepNoWB(unsafe.Pointer(&p[i]), unsafe.Pointer(new(int)))
|
|
}
|
|
if p[0] == p[1] {
|
|
t.Error("Bad escape analysis of StorepNoWB")
|
|
}
|
|
}
|