Repository: uber-go/atomic (go)

Issue:
Shall we add support of uintptr and Pointer?
I'll be glad to work on it if it is good to go.

```go
func LoadUintptr(addr *uintptr) (val uintptr)
func StoreUintptr(addr *uintptr, val uintptr)
func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)
func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)
func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)

func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
```


Discussion:
Yes, this sounds reasonable. We would welcome a PR for this! Although for unsafe.Pointer, I would be reluctant to name it `atomic.Pointer`. Maybe we can name it `UnsafePointer` (used as `atomic.UnsafePointer`) to make it clear this is still an unsafe API.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [Unreleased]
+### Added
+- Add `atomic.Uintptr` type for atomic operations on `uintptr` values.
+- Add `atomic.UnsafePointer` type for atomic operations on `unsafe.Pointer` values.
+
 ## [1.7.0] - 2020-09-14
 ### Added
 - Support JSON serialization and deserialization of primitive atomic types.
diff --git a/gen.go b/gen.go
--- a/gen.go
+++ b/gen.go
@@ -24,3 +24,4 @@ package atomic
 //go:generate bin/gen-atomicint -name=Int64 -wrapped=int64 -file=int64.go
 //go:generate bin/gen-atomicint -name=Uint32 -wrapped=uint32 -unsigned -file=uint32.go
 //go:generate bin/gen-atomicint -name=Uint64 -wrapped=uint64 -unsigned -file=uint64.go
+//go:generate bin/gen-atomicint -name=Uintptr -wrapped=uintptr -unsigned -file=uintptr.go
diff --git a/uintptr.go b/uintptr.go
new file mode 100644
--- /dev/null
+++ b/uintptr.go
@@ -0,0 +1,102 @@
+// @generated Code generated by gen-atomicint.
+
+// Copyright (c) 2020-2021 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+import (
+	"encoding/json"
+	"strconv"
+	"sync/atomic"
+)
+
+// Uintptr is an atomic wrapper around uintptr.
+type Uintptr struct {
+	_ nocmp // disallow non-atomic comparison
+
+	v uintptr
+}
+
+// NewUintptr creates a new Uintptr.
+func NewUintptr(i uintptr) *Uintptr {
+	return &Uintptr{v: i}
+}
+
+// Load atomically loads the wrapped value.
+func (i *Uintptr) Load() uintptr {
+	return atomic.LoadUintptr(&i.v)
+}
+
+// Add atomically adds to the wrapped uintptr and returns the new value.
+func (i *Uintptr) Add(n uintptr) uintptr {
+	return atomic.AddUintptr(&i.v, n)
+}
+
+// Sub atomically subtracts from the wrapped uintptr and returns the new value.
+func (i *Uintptr) Sub(n uintptr) uintptr {
+	return atomic.AddUintptr(&i.v, ^(n - 1))
+}
+
+// Inc atomically increments the wrapped uintptr and returns the new value.
+func (i *Uintptr) Inc() uintptr {
+	return i.Add(1)
+}
+
+// Dec atomically decrements the wrapped uintptr and returns the new value.
+func (i *Uintptr) Dec() uintptr {
+	return i.Sub(1)
+}
+
+// CAS is an atomic compare-and-swap.
+func (i *Uintptr) CAS(old, new uintptr) bool {
+	return atomic.CompareAndSwapUintptr(&i.v, old, new)
+}
+
+// Store atomically stores the passed value.
+func (i *Uintptr) Store(n uintptr) {
+	atomic.StoreUintptr(&i.v, n)
+}
+
+// Swap atomically swaps the wrapped uintptr and returns the old value.
+func (i *Uintptr) Swap(n uintptr) uintptr {
+	return atomic.SwapUintptr(&i.v, n)
+}
+
+// MarshalJSON encodes the wrapped uintptr into JSON.
+func (i *Uintptr) MarshalJSON() ([]byte, error) {
+	return json.Marshal(i.Load())
+}
+
+// UnmarshalJSON decodes JSON into the wrapped uintptr.
+func (i *Uintptr) UnmarshalJSON(b []byte) error {
+	var v uintptr
+	if err := json.Unmarshal(b, &v); err != nil {
+		return err
+	}
+	i.Store(v)
+	return nil
+}
+
+// String encodes the wrapped value as a string.
+func (i *Uintptr) String() string {
+	v := i.Load()
+	return strconv.FormatUint(uint64(v), 10)
+}
diff --git a/unsafe_pointer.go b/unsafe_pointer.go
new file mode 100644
--- /dev/null
+++ b/unsafe_pointer.go
@@ -0,0 +1,58 @@
+// Copyright (c) 2021 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+import (
+	"sync/atomic"
+	"unsafe"
+)
+
+// UnsafePointer is an atomic wrapper around unsafe.Pointer.
+type UnsafePointer struct {
+	_ nocmp // disallow non-atomic comparison
+
+	v unsafe.Pointer
+}
+
+// NewUnsafePointer creates a new UnsafePointer.
+func NewUnsafePointer(p unsafe.Pointer) *UnsafePointer {
+	return &UnsafePointer{v: p}
+}
+
+// Load atomically loads the wrapped value.
+func (p *UnsafePointer) Load() unsafe.Pointer {
+	return atomic.LoadPointer(&p.v)
+}
+
+// Store atomically stores the passed value.
+func (p *UnsafePointer) Store(q unsafe.Pointer) {
+	atomic.StorePointer(&p.v, q)
+}
+
+// Swap atomically swaps the wrapped unsafe.Pointer and returns the old value.
+func (p *UnsafePointer) Swap(q unsafe.Pointer) unsafe.Pointer {
+	return atomic.SwapPointer(&p.v, q)
+}
+
+// CAS is an atomic compare-and-swap.
+func (p *UnsafePointer) CAS(old, new unsafe.Pointer) bool {
+	return atomic.CompareAndSwapPointer(&p.v, old, new)
+}


Test patch:
diff --git a/uintptr_test.go b/uintptr_test.go
new file mode 100644
--- /dev/null
+++ b/uintptr_test.go
@@ -0,0 +1,76 @@
+// Copyright (c) 2021 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+import (
+	"encoding/json"
+	"math"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestUintptr(t *testing.T) {
+	atom := NewUintptr(42)
+
+	require.Equal(t, uintptr(42), atom.Load(), "Load didn't work.")
+	require.Equal(t, uintptr(46), atom.Add(4), "Add didn't work.")
+	require.Equal(t, uintptr(44), atom.Sub(2), "Sub didn't work.")
+	require.Equal(t, uintptr(45), atom.Inc(), "Inc didn't work.")
+	require.Equal(t, uintptr(44), atom.Dec(), "Dec didn't work.")
+
+	require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
+	require.Equal(t, uintptr(0), atom.Load(), "CAS didn't set the correct value.")
+
+	require.Equal(t, uintptr(0), atom.Swap(1), "Swap didn't return the old value.")
+	require.Equal(t, uintptr(1), atom.Load(), "Swap didn't set the correct value.")
+
+	atom.Store(42)
+	require.Equal(t, uintptr(42), atom.Load(), "Store didn't set the correct value.")
+
+	t.Run("JSON/Marshal", func(t *testing.T) {
+		bytes, err := json.Marshal(atom)
+		require.NoError(t, err, "json.Marshal errored unexpectedly.")
+		require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.")
+	})
+
+	t.Run("JSON/Unmarshal", func(t *testing.T) {
+		err := json.Unmarshal([]byte("40"), &atom)
+		require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
+		require.Equal(t, uintptr(40), atom.Load(), "json.Unmarshal didn't set the correct value.")
+	})
+
+	t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
+		err := json.Unmarshal([]byte(`"40"`), &atom)
+		require.Error(t, err, "json.Unmarshal didn't error as expected.")
+		assertErrorJSONUnmarshalType(t, err,
+			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
+	})
+
+	t.Run("String", func(t *testing.T) {
+		// Use an integer with the signed bit set. If we're converting
+		// incorrectly, we'll get a negative value here.
+		atom := NewUintptr(uintptr(math.MaxUint64))
+		assert.Equal(t, "18446744073709551615", atom.String(),
+			"String() returned an unexpected value.")
+	})
+}
diff --git a/unsafe_pointer_test.go b/unsafe_pointer_test.go
new file mode 100644
--- /dev/null
+++ b/unsafe_pointer_test.go
@@ -0,0 +1,83 @@
+// Copyright (c) 2021 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+import (
+	"testing"
+	"unsafe"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestUnsafePointer(t *testing.T) {
+	i := int64(42)
+	j := int64(0)
+	k := int64(1)
+
+	tests := []struct {
+		desc      string
+		newAtomic func() *UnsafePointer
+		initial   unsafe.Pointer
+	}{
+		{
+			desc: "non-empty",
+			newAtomic: func() *UnsafePointer {
+				return NewUnsafePointer(unsafe.Pointer(&i))
+			},
+			initial: unsafe.Pointer(&i),
+		},
+		{
+			desc: "nil",
+			newAtomic: func() *UnsafePointer {
+				var p UnsafePointer
+				return &p
+			},
+			initial: unsafe.Pointer(nil),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.desc, func(t *testing.T) {
+			t.Run("Load", func(t *testing.T) {
+				atom := tt.newAtomic()
+				require.Equal(t, tt.initial, atom.Load(), "Load should report nil.")
+			})
+
+			t.Run("Swap", func(t *testing.T) {
+				atom := tt.newAtomic()
+				require.Equal(t, tt.initial, atom.Swap(unsafe.Pointer(&k)), "Swap didn't return the old value.")
+				require.Equal(t, unsafe.Pointer(&k), atom.Load(), "Swap didn't set the correct value.")
+			})
+
+			t.Run("CAS", func(t *testing.T) {
+				atom := tt.newAtomic()
+				require.True(t, atom.CAS(tt.initial, unsafe.Pointer(&j)), "CAS didn't report a swap.")
+				require.Equal(t, unsafe.Pointer(&j), atom.Load(), "CAS didn't set the correct value.")
+			})
+
+			t.Run("Store", func(t *testing.T) {
+				atom := tt.newAtomic()
+				atom.Store(unsafe.Pointer(&i))
+				require.Equal(t, unsafe.Pointer(&i), atom.Load(), "Store didn't set the correct value.")
+			})
+		})
+	}
+}