Skip to content

Commit bbec3a5

Browse files
committed
runtime/internal/atomic: add wasm And/Or operators
DO NOT REVIEW DO NOT SUBMIT In the WebAssembly version of these operators we avoid using a CAS loop since the Go wasm implementation is single-threaded. A new test file has been added that has build tags in order to only test this feature on supported architectures. This is part of a series of CLs aimed to add the primitives for And/Or atomic operations that will be used by the public sync/atomic apis. For #61395
1 parent 02d581e commit bbec3a5

File tree

2 files changed

+217
-0
lines changed

2 files changed

+217
-0
lines changed
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// +build wasm
2+
//
3+
// Copyright 2023 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
// TODO(61395): move these tests to atomic_test.go once And/Or have
8+
// implementations for all architectures.
9+
package atomic_test
10+
11+
import (
12+
"testing"
13+
"runtime/internal/atomic"
14+
)
15+
16+
func TestAnd32(t *testing.T) {
17+
// Basic sanity check.
18+
x := uint32(0xffffffff)
19+
for i := uint32(0); i < 32; i++ {
20+
old := x
21+
v := atomic.And32(&x, ^(1 << i))
22+
if r := uint32(0xffffffff) << (i + 1); x != r || v != old {
23+
t.Fatalf("clearing bit %#x: want %#x, got new %#x and old %#v", uint32(1<<i), r, x, v)
24+
}
25+
}
26+
27+
// Set every bit in array to 1.
28+
a := make([]uint32, 1<<12)
29+
for i := range a {
30+
a[i] = 0xffffffff
31+
}
32+
33+
// Clear array bit-by-bit in different goroutines.
34+
done := make(chan bool)
35+
for i := 0; i < 32; i++ {
36+
m := ^uint32(1 << i)
37+
go func() {
38+
for i := range a {
39+
atomic.And(&a[i], m)
40+
}
41+
done <- true
42+
}()
43+
}
44+
for i := 0; i < 32; i++ {
45+
<-done
46+
}
47+
48+
// Check that the array has been totally cleared.
49+
for i, v := range a {
50+
if v != 0 {
51+
t.Fatalf("a[%v] not cleared: want %#x, got %#x", i, uint32(0), v)
52+
}
53+
}
54+
}
55+
56+
func TestAnd64(t *testing.T) {
57+
// Basic sanity check.
58+
x := uint64(0xffffffffffffffff)
59+
for i := uint64(0); i < 64; i++ {
60+
old := x
61+
v := atomic.And64(&x, ^(1 << i))
62+
if r := uint64(0xffffffffffffffff) << (i + 1); x != r || v != old {
63+
t.Fatalf("clearing bit %#x: want %#x, got new %#x and old %#v", uint64(1<<i), r, x, v)
64+
}
65+
}
66+
67+
// Set every bit in array to 1.
68+
a := make([]uint64, 1<<12)
69+
for i := range a {
70+
a[i] = 0xffffffffffffffff
71+
}
72+
73+
// Clear array bit-by-bit in different goroutines.
74+
done := make(chan bool)
75+
for i := 0; i < 64; i++ {
76+
m := ^uint64(1 << i)
77+
go func() {
78+
for i := range a {
79+
atomic.And64(&a[i], m)
80+
}
81+
done <- true
82+
}()
83+
}
84+
for i := 0; i < 64; i++ {
85+
<-done
86+
}
87+
88+
// Check that the array has been totally cleared.
89+
for i, v := range a {
90+
if v != 0 {
91+
t.Fatalf("a[%v] not cleared: want %#x, got %#x", i, uint64(0), v)
92+
}
93+
}
94+
}
95+
96+
func TestOr32(t *testing.T) {
97+
// Basic sanity check.
98+
x := uint32(0)
99+
for i := uint32(0); i < 32; i++ {
100+
old := x
101+
v := atomic.Or32(&x, 1<<i)
102+
if r := (uint32(1) << (i + 1)) - 1; x != r || v != old {
103+
t.Fatalf("setting bit %#x: want %#x, got new %#x and old %#v", uint32(1<<i), r, x, v)
104+
}
105+
}
106+
107+
// Start with every bit in array set to 0.
108+
a := make([]uint32, 1<<12)
109+
110+
// Set every bit in array bit-by-bit in different goroutines.
111+
done := make(chan bool)
112+
for i := 0; i < 32; i++ {
113+
m := uint32(1 << i)
114+
go func() {
115+
for i := range a {
116+
atomic.Or32(&a[i], m)
117+
}
118+
done <- true
119+
}()
120+
}
121+
for i := 0; i < 32; i++ {
122+
<-done
123+
}
124+
125+
// Check that the array has been totally set.
126+
for i, v := range a {
127+
if v != 0xffffffff {
128+
t.Fatalf("a[%v] not fully set: want %#x, got %#x", i, uint32(0xffffffff), v)
129+
}
130+
}
131+
}
132+
133+
func TestOr64(t *testing.T) {
134+
// Basic sanity check.
135+
x := uint64(0)
136+
for i := uint64(0); i < 64; i++ {
137+
old := x
138+
v := atomic.Or64(&x, 1<<i)
139+
if r := (uint64(1) << (i + 1)) - 1; x != r || v != old {
140+
t.Fatalf("setting bit %#x: want %#x, got new %#x and old %#v", uint64(1<<i), r, x, v)
141+
}
142+
}
143+
144+
// Start with every bit in array set to 0.
145+
a := make([]uint64, 1<<12)
146+
147+
// Set every bit in array bit-by-bit in different goroutines.
148+
done := make(chan bool)
149+
for i := 0; i < 64; i++ {
150+
m := uint64(1 << i)
151+
go func() {
152+
for i := range a {
153+
atomic.Or64(&a[i], m)
154+
}
155+
done <- true
156+
}()
157+
}
158+
for i := 0; i < 64; i++ {
159+
<-done
160+
}
161+
162+
// Check that the array has been totally set.
163+
for i, v := range a {
164+
if v != 0xffffffffffffffff {
165+
t.Fatalf("a[%v] not fully set: want %#x, got %#x", i, uint64(0xffffffffffffffff), v)
166+
}
167+
}
168+
}
169+

src/runtime/internal/atomic/atomic_wasm.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,3 +339,51 @@ func Xaddint64(ptr *int64, delta int64) int64 {
339339
*ptr = new
340340
return new
341341
}
342+
343+
//go:nosplit
344+
//go:noinline
345+
func And32(ptr *uint32, val uint32) uint32 {
346+
old := *ptr
347+
*ptr = old & val
348+
return old
349+
}
350+
351+
//go:nosplit
352+
//go:noinline
353+
func And64(ptr *uint64, val uint64) uint64 {
354+
old := *ptr
355+
*ptr = old & val
356+
return old
357+
}
358+
359+
//go:nosplit
360+
//go:noinline
361+
func Anduintptr(ptr *uintptr, val uintptr) uintptr {
362+
old := *ptr
363+
*ptr = old & val
364+
return old
365+
}
366+
367+
//go:nosplit
368+
//go:noinline
369+
func Or32(ptr *uint32, val uint32) uint32 {
370+
old := *ptr
371+
*ptr = old | val
372+
return old
373+
}
374+
375+
//go:nosplit
376+
//go:noinline
377+
func Or64(ptr *uint64, val uint64) uint64 {
378+
old := *ptr
379+
*ptr = old | val
380+
return old
381+
}
382+
383+
//go:nosplit
384+
//go:noinline
385+
func Oruintptr(ptr *uintptr, val uintptr) uintptr {
386+
old := *ptr
387+
*ptr = old | val
388+
return old
389+
}

0 commit comments

Comments
 (0)