diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66f3ff2..f953994 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -135,7 +135,7 @@ jobs: - name: Run tests run: | cd threading - nimble test + nimble --threads:on test # - name: Build docs # if: matrix.branch == 'devel' diff --git a/tests/fake_test.nim b/tests/fake_test.nim deleted file mode 100644 index c39a9c1..0000000 --- a/tests/fake_test.nim +++ /dev/null @@ -1 +0,0 @@ -echo "this is fake test" diff --git a/tests/nim.cfg b/tests/nim.cfg new file mode 100644 index 0000000..37e96f0 --- /dev/null +++ b/tests/nim.cfg @@ -0,0 +1 @@ +--path:"../" diff --git a/tests/tatomics.nim b/tests/tatomics.nim new file mode 100644 index 0000000..17636ce --- /dev/null +++ b/tests/tatomics.nim @@ -0,0 +1,319 @@ +import std/bitops, threading/atomics +# Atomic operations for trivial objects + +block trivialLoad: + var location: Atomic[int] + location.store(1) + assert location.load == 1 + location.store(2) + assert location.load(Relaxed) == 2 + location.store(3) + assert location.load(Acquire) == 3 + +block trivialStore: + var location: Atomic[int] + location.store(1) + assert location.load == 1 + location.store(2, Relaxed) + assert location.load == 2 + location.store(3, Release) + assert location.load == 3 + +block trivialExchange: + var location: Atomic[int] + location.store(1) + assert location.exchange(2) == 1 + assert location.exchange(3, Relaxed) == 2 + assert location.exchange(4, Acquire) == 3 + assert location.exchange(5, Release) == 4 + assert location.exchange(6, AcqRel) == 5 + assert location.load == 6 + +block trivialCompareExchangeDoesExchange: + var location: Atomic[int] + var expected = 1 + location.store(1) + assert location.compareExchange(expected, 2) + assert expected == 1 + assert location.load == 2 + expected = 2 + assert location.compareExchange(expected, 3, Relaxed) + assert expected == 2 + assert location.load == 3 + expected = 3 + assert location.compareExchange(expected, 4, Acquire) + assert expected == 3 + assert location.load == 4 + expected = 4 + assert location.compareExchange(expected, 5, Release) + assert expected == 4 + assert location.load == 5 + expected = 5 + assert location.compareExchange(expected, 6, AcqRel) + assert expected == 5 + assert location.load == 6 + +block trivialCompareExchangeDoesNotExchange: + var location: Atomic[int] + var expected = 10 + location.store(1) + assert not location.compareExchange(expected, 2) + assert expected == 1 + assert location.load == 1 + expected = 10 + assert not location.compareExchange(expected, 3, Relaxed) + assert expected == 1 + assert location.load == 1 + expected = 10 + assert not location.compareExchange(expected, 4, Acquire) + assert expected == 1 + assert location.load == 1 + expected = 10 + assert not location.compareExchange(expected, 5, Release) + assert expected == 1 + assert location.load == 1 + expected = 10 + assert not location.compareExchange(expected, 6, AcqRel) + assert expected == 1 + assert location.load == 1 + +block trivialCompareExchangeSuccessFailureDoesExchange: + var location: Atomic[int] + var expected = 1 + location.store(1) + assert location.compareExchange(expected, 2, SeqCst, SeqCst) + assert expected == 1 + assert location.load == 2 + expected = 2 + assert location.compareExchange(expected, 3, Relaxed, Relaxed) + assert expected == 2 + assert location.load == 3 + expected = 3 + assert location.compareExchange(expected, 4, Acquire, Acquire) + assert expected == 3 + assert location.load == 4 + expected = 4 + assert location.compareExchange(expected, 5, Release, Release) + assert expected == 4 + assert location.load == 5 + expected = 5 + assert location.compareExchange(expected, 6, AcqRel, AcqRel) + assert expected == 5 + assert location.load == 6 + +block trivialCompareExchangeSuccessFailureDoesNotExchange: + var location: Atomic[int] + var expected = 10 + location.store(1) + assert not location.compareExchange(expected, 2, SeqCst, SeqCst) + assert expected == 1 + assert location.load == 1 + expected = 10 + assert not location.compareExchange(expected, 3, Relaxed, Relaxed) + assert expected == 1 + assert location.load == 1 + expected = 10 + assert not location.compareExchange(expected, 4, Acquire, Acquire) + assert expected == 1 + assert location.load == 1 + expected = 10 + assert not location.compareExchange(expected, 5, Release, Release) + assert expected == 1 + assert location.load == 1 + expected = 10 + assert not location.compareExchange(expected, 6, AcqRel, AcqRel) + assert expected == 1 + assert location.load == 1 + +block trivialCompareExchangeWeakDoesExchange: + var location: Atomic[int] + var expected = 1 + location.store(1) + assert location.compareExchangeWeak(expected, 2) + assert expected == 1 + assert location.load == 2 + expected = 2 + assert location.compareExchangeWeak(expected, 3, Relaxed) + assert expected == 2 + assert location.load == 3 + expected = 3 + assert location.compareExchangeWeak(expected, 4, Acquire) + assert expected == 3 + assert location.load == 4 + expected = 4 + assert location.compareExchangeWeak(expected, 5, Release) + assert expected == 4 + assert location.load == 5 + expected = 5 + assert location.compareExchangeWeak(expected, 6, AcqRel) + assert expected == 5 + assert location.load == 6 + +block trivialCompareExchangeWeakDoesNotExchange: + var location: Atomic[int] + var expected = 10 + location.store(1) + assert not location.compareExchangeWeak(expected, 2) + assert expected == 1 + assert location.load == 1 + expected = 10 + assert not location.compareExchangeWeak(expected, 3, Relaxed) + assert expected == 1 + assert location.load == 1 + expected = 10 + assert not location.compareExchangeWeak(expected, 4, Acquire) + assert expected == 1 + assert location.load == 1 + expected = 10 + assert not location.compareExchangeWeak(expected, 5, Release) + assert expected == 1 + assert location.load == 1 + expected = 10 + assert not location.compareExchangeWeak(expected, 6, AcqRel) + assert expected == 1 + assert location.load == 1 + +block trivialCompareExchangeWeakSuccessFailureDoesExchange: + var location: Atomic[int] + var expected = 1 + location.store(1) + assert location.compareExchangeWeak(expected, 2, SeqCst, SeqCst) + assert expected == 1 + assert location.load == 2 + expected = 2 + assert location.compareExchangeWeak(expected, 3, Relaxed, Relaxed) + assert expected == 2 + assert location.load == 3 + expected = 3 + assert location.compareExchangeWeak(expected, 4, Acquire, Acquire) + assert expected == 3 + assert location.load == 4 + expected = 4 + assert location.compareExchangeWeak(expected, 5, Release, Release) + assert expected == 4 + assert location.load == 5 + expected = 5 + assert location.compareExchangeWeak(expected, 6, AcqRel, AcqRel) + assert expected == 5 + assert location.load == 6 + +block trivialCompareExchangeWeakSuccessFailureDoesNotExchange: + var location: Atomic[int] + var expected = 10 + location.store(1) + assert not location.compareExchangeWeak(expected, 2, SeqCst, SeqCst) + assert expected == 1 + assert location.load == 1 + expected = 10 + assert not location.compareExchangeWeak(expected, 3, Relaxed, Relaxed) + assert expected == 1 + assert location.load == 1 + expected = 10 + assert not location.compareExchangeWeak(expected, 4, Acquire, Acquire) + assert expected == 1 + assert location.load == 1 + expected = 10 + assert not location.compareExchangeWeak(expected, 5, Release, Release) + assert expected == 1 + assert location.load == 1 + expected = 10 + assert not location.compareExchangeWeak(expected, 6, AcqRel, AcqRel) + assert expected == 1 + assert location.load == 1 + +# Numerical operations + +block fetchAdd: + var location: Atomic[int] + assert location.fetchAdd(1) == 0 + assert location.fetchAdd(1, Relaxed) == 1 + assert location.fetchAdd(1, Release) == 2 + assert location.load == 3 + +block fetchSub: + var location: Atomic[int] + assert location.fetchSub(1) == 0 + assert location.fetchSub(1, Relaxed) == -1 + assert location.fetchSub(1, Release) == -2 + assert location.load == -3 + +block fetchAnd: + var location: Atomic[int] + + for i in 0..16: + for j in 0..16: + location.store(i) + assert(location.fetchAnd(j) == i) + assert(location.load == i.bitand(j)) + + for i in 0..16: + for j in 0..16: + location.store(i) + assert(location.fetchAnd(j, Relaxed) == i) + assert(location.load == i.bitand(j)) + + for i in 0..16: + for j in 0..16: + location.store(i) + assert(location.fetchAnd(j, Release) == i) + assert(location.load == i.bitand(j)) + +block fetchOr: + var location: Atomic[int] + + for i in 0..16: + for j in 0..16: + location.store(i) + assert(location.fetchOr(j) == i) + assert(location.load == i.bitor(j)) + + for i in 0..16: + for j in 0..16: + location.store(i) + assert(location.fetchOr(j, Relaxed) == i) + assert(location.load == i.bitor(j)) + + for i in 0..16: + for j in 0..16: + location.store(i) + assert(location.fetchOr(j, Release) == i) + assert(location.load == i.bitor(j)) + +block fetchXor: + var location: Atomic[int] + + for i in 0..16: + for j in 0..16: + location.store(i) + assert(location.fetchXor(j) == i) + assert(location.load == i.bitxor(j)) + + for i in 0..16: + for j in 0..16: + location.store(i) + assert(location.fetchXor(j, Relaxed) == i) + assert(location.load == i.bitxor(j)) + + for i in 0..16: + for j in 0..16: + location.store(i) + assert(location.fetchXor(j, Release) == i) + assert(location.load == i.bitxor(j)) + +block atomicInc: + var location: Atomic[int] + location.atomicInc + assert location.load == 1 + location.atomicInc(1) + assert location.load == 2 + location += 1 + assert location.load == 3 + +block atomicDec: + var location: Atomic[int] + location.atomicDec + assert location.load == -1 + location.atomicDec(1) + assert location.load == -2 + location -= 1 + assert location.load == -3 diff --git a/tests/tsmartptrs.nim b/tests/tsmartptrs.nim new file mode 100644 index 0000000..3f01a67 --- /dev/null +++ b/tests/tsmartptrs.nim @@ -0,0 +1,63 @@ +import threading/smartptrs + +block: + var a1: UniquePtr[int] + var a2 = newUniquePtr(0) + + assert $a1 == "nil" + assert a1.isNil + assert $a2 == "(val: 0)" + assert not a2.isNil + assert a2[] == 0 + + # UniquePtr can't be copied but can be moved + let a3 = move a2 + + assert $a2 == "nil" + assert a2.isNil + + assert $a3 == "(val: 0)" + assert not a3.isNil + assert a3[] == 0 + + a1 = newUniquePtr(int) + a1[] = 1 + assert a1[] == 1 + var a4 = newUniquePtr(string) + a4[] = "hello world" + assert a4[] == "hello world" + +block: + var a1: SharedPtr[int] + let a2 = newSharedPtr(0) + let a3 = a2 + + assert $a1 == "nil" + assert a1.isNil + assert $a2 == "(val: 0)" + assert not a2.isNil + assert a2[] == 0 + assert $a3 == "(val: 0)" + assert not a3.isNil + assert a3[] == 0 + + a1 = newSharedPtr(int) + a1[] = 1 + assert a1[] == 1 + var a4 = newSharedPtr(string) + a4[] = "hello world" + assert a4[] == "hello world" + +block: + var a1: ConstPtr[float] + let a2 = newConstPtr(0) + let a3 = a2 + + assert $a1 == "nil" + assert a1.isNil + assert $a2 == "(val: 0)" + assert not a2.isNil + assert a2[] == 0 + assert $a3 == "(val: 0)" + assert not a3.isNil + assert a3[] == 0 diff --git a/threading.nimble b/threading.nimble index 3d927d4..2b0a402 100644 --- a/threading.nimble +++ b/threading.nimble @@ -10,7 +10,3 @@ srcDir = "src" # Dependencies requires "nim >= 1.4" - - -task test, "runs the tests": - exec "nim c -r tests/fake_test.nim" diff --git a/threading/atomics.nim b/threading/atomics.nim new file mode 100644 index 0000000..e4f922e --- /dev/null +++ b/threading/atomics.nim @@ -0,0 +1,171 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2021 Nim Contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Types and operations for atomic operations and lockless algorithms. +runnableExamples("--threads:on"): + # Atomic + var loc: Atomic[int] + loc.store(4) + assert loc.load == 4 + loc.store(2) + assert loc.load(Relaxed) == 2 + loc.store(9) + assert loc.load(Acquire) == 9 + loc.store(0, Release) + assert loc.load == 0 + + assert loc.exchange(7) == 0 + assert loc.load == 7 + + var expected = 7 + assert loc.compareExchange(expected, 5, Relaxed, Relaxed) + assert expected == 7 + assert loc.load == 5 + + assert not loc.compareExchange(expected, 12, Relaxed, Relaxed) + assert expected == 5 + assert loc.load == 5 + + assert loc.fetchAdd(1) == 5 + assert loc.fetchAdd(2) == 6 + assert loc.fetchSub(3) == 8 + + loc.atomicInc(1) + assert loc.load == 6 + +when not compileOption("threads"): + {.error: "This module requires --threads:on compilation flag".} + +type + Ordering* {.pure.} = enum + ## Specifies how non-atomic operations can be reordered around atomic + ## operations. + Relaxed + ## No ordering constraints. Only the atomicity and ordering against + ## other atomic operations is guaranteed. + Consume + ## This ordering is currently discouraged as it's semantics are + ## being revised. Acquire operations should be preferred. + Acquire + ## When applied to a load operation, no reads or writes in the + ## current thread can be reordered before this operation. + Release + ## When applied to a store operation, no reads or writes in the + ## current thread can be reorderd after this operation. + AcqRel + ## When applied to a read-modify-write operation, this behaves like + ## both an acquire and a release operation. + SeqCst + ## Behaves like Acquire when applied to load, like Release when + ## applied to a store and like AcquireRelease when applied to a + ## read-modify-write operation. + ## Also guarantees that all threads observe the same total ordering + ## with other SeqCst operations. + +type + Atomic*[T: AtomType] = distinct T ## An atomic object with underlying type `T`. + +proc `=copy`*[T](dst: var Atomic[T]; src: Atomic[T]) = + atomicStoreN(addr T(dst), T(src), AtomicSeqCst) + +proc `=sink`*[T](dst: var Atomic[T]; src: Atomic[T]) = + atomicStoreN(addr T(dst), T(src), AtomicSeqCst) + +proc load*[T](location: var Atomic[T]; order: Ordering = SeqCst): T = + ## Atomically obtains the value of the atomic object. + atomicLoadN(addr T(location), AtomMemModel(order)) + +proc store*[T](location: var Atomic[T]; desired: T; order: Ordering = SeqCst) = + ## Atomically replaces the value of the atomic object with the `desired` + ## value. + atomicStoreN(addr T(location), desired, AtomMemModel(order)) + +proc exchange*[T](location: var Atomic[T]; desired: T; + order: Ordering = SeqCst): T = + ## Atomically replaces the value of the atomic object with the `desired` + ## value and returns the old value. + atomicExchangeN(addr T(location), desired, AtomMemModel(order)) + +proc compareExchange*[T](location: var Atomic[T]; expected: var T; desired: T; + order: Ordering = SeqCst): bool = + ## Atomically compares the value of the atomic object with the `expected` + ## value and performs exchange with the `desired` one if equal or load if + ## not. Returns true if the exchange was successful. + atomicCompareExchangeN(addr T(location), addr expected, desired, + false, AtomMemModel(order), AtomMemModel(order)) + +proc compareExchange*[T](location: var Atomic[T]; expected: var T; desired: T; + success, failure: Ordering): bool = + ## Same as above, but allows for different memory orders for success and + ## failure. + atomicCompareExchangeN(addr T(location), addr expected, desired, + false, AtomMemModel(success), AtomMemModel(failure)) + +proc compareExchangeWeak*[T](location: var Atomic[T]; expected: var T; + desired: T; order: Ordering = SeqCst): bool = + ## Same as above, but is allowed to fail spuriously. + atomicCompareExchangeN(addr T(location), addr expected, desired, + true, AtomMemModel(order), AtomMemModel(order)) + +proc compareExchangeWeak*[T](location: var Atomic[T]; expected: var T; + desired: T; success, failure: Ordering): bool = + ## Same as above, but allows for different memory orders for success and + ## failure. + atomicCompareExchangeN(addr T(location), addr expected, desired, + true, AtomMemModel(success), AtomMemModel(failure)) + +# Numerical operations + +proc fetchAdd*[T: SomeInteger](location: var Atomic[T]; value: T; + order: Ordering = SeqCst): T = + ## Atomically adds a `value` to the atomic integer and returns the + ## original value. + atomicFetchAdd(addr T(location), value, AtomMemModel(order)) + +proc fetchSub*[T: SomeInteger](location: var Atomic[T]; value: T; + order: Ordering = SeqCst): T = + ## Atomically subtracts a `value` to the atomic integer and returns the + ## original value. + atomicFetchSub(addr T(location), value, AtomMemModel(order)) + +proc fetchAnd*[T: SomeInteger](location: var Atomic[T]; value: T; + order: Ordering = SeqCst): T = + ## Atomically replaces the atomic integer with it's bitwise AND + ## with the specified `value` and returns the original value. + atomicFetchAnd(addr T(location), value, AtomMemModel(order)) + +proc fetchOr*[T: SomeInteger](location: var Atomic[T]; value: T; + order: Ordering = SeqCst): T = + ## Atomically replaces the atomic integer with it's bitwise OR + ## with the specified `value` and returns the original value. + atomicFetchOr(addr T(location), value, AtomMemModel(order)) + +proc fetchXor*[T: SomeInteger](location: var Atomic[T]; value: T; + order: Ordering = SeqCst): T = + ## Atomically replaces the atomic integer with it's bitwise XOR + ## with the specified `value` and returns the original value. + atomicFetchXor(addr T(location), value, AtomMemModel(order)) + +proc atomicInc*[T: SomeInteger](location: var Atomic[T]; + value: T = 1) {.inline.} = + ## Atomically increments the atomic integer by some `value`. + discard location.fetchAdd(value) + +proc atomicDec*[T: SomeInteger](location: var Atomic[T]; + value: T = 1) {.inline.} = + ## Atomically decrements the atomic integer by some `value`. + discard location.fetchSub(value) + +proc `+=`*[T: SomeInteger](location: var Atomic[T]; value: T) {.inline.} = + ## Atomically increments the atomic integer by some `value`. + discard location.fetchAdd(value) + +proc `-=`*[T: SomeInteger](location: var Atomic[T]; value: T) {.inline.} = + ## Atomically decrements the atomic integer by some `value`. + discard location.fetchSub(value) diff --git a/threading/smartptrs.nim b/threading/smartptrs.nim new file mode 100644 index 0000000..25480b7 --- /dev/null +++ b/threading/smartptrs.nim @@ -0,0 +1,158 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2021 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. + +## C++11 like smart pointers. They always use the shared allocator. +import std/isolation, atomics +from typetraits import supportsCopyMem + +proc raiseNilAccess() {.noinline.} = + raise newException(NilAccessDefect, "dereferencing nil smart pointer") + +template checkNotNil(p: typed) = + when compileOption("boundChecks"): + {.line.}: + if p.isNil: + raiseNilAccess() + +type + UniquePtr*[T] = object + ## Non copyable pointer to a value of type `T` with exclusive ownership. + val: ptr T + +proc `=destroy`*[T](p: var UniquePtr[T]) = + if p.val != nil: + `=destroy`(p.val[]) + deallocShared(p.val) + +proc `=copy`*[T](dest: var UniquePtr[T], src: UniquePtr[T]) {.error.} + ## The copy operation is disallowed for `UniquePtr`, it + ## can only be moved. + +proc newUniquePtr*[T](val: sink Isolated[T]): UniquePtr[T] {.nodestroy.} = + ## Returns a unique pointer which has exclusive ownership of the value. + result.val = cast[ptr T](allocShared(sizeof(T))) + # thanks to '.nodestroy' we don't have to use allocShared0 here. + # This is compiled into a copyMem operation, no need for a sink + # here either. + result.val[] = extract val + # no destructor call for 'val: sink T' here either. + +template newUniquePtr*[T](val: T): UniquePtr[T] = + newUniquePtr(isolate(val)) + +proc newUniquePtr*[T](t: typedesc[T]): UniquePtr[T] = + ## Returns a unique pointer. It is not initialized, + ## so reading from it before writing to it is undefined behaviour! + when not supportsCopyMem(T): + result.val = cast[ptr T](allocShared0(sizeof(T))) + else: + result.val = cast[ptr T](allocShared(sizeof(T))) + +proc isNil*[T](p: UniquePtr[T]): bool {.inline.} = + p.val == nil + +proc `[]`*[T](p: UniquePtr[T]): var T {.inline.} = + ## Returns a mutable view of the internal value of `p`. + checkNotNil(p) + p.val[] + +proc `[]=`*[T](p: UniquePtr[T], val: sink Isolated[T]) {.inline.} = + checkNotNil(p) + p.val[] = extract val + +template `[]=`*[T](p: UniquePtr[T]; val: T) = + `[]=`(p, isolate(val)) + +proc `$`*[T](p: UniquePtr[T]): string {.inline.} = + if p.val == nil: "nil" + else: "(val: " & $p.val[] & ")" + +#------------------------------------------------------------------------------ + +type + SharedPtr*[T] = object + ## Shared ownership reference counting pointer. + val: ptr tuple[value: T, counter: Atomic[int]] + +proc `=destroy`*[T](p: var SharedPtr[T]) = + if p.val != nil: + if p.val[].counter.load(Consume) == 0: + `=destroy`(p.val[]) + deallocShared(p.val) + else: + atomicDec(p.val[].counter) + +proc `=copy`*[T](dest: var SharedPtr[T], src: SharedPtr[T]) = + if src.val != nil: + atomicInc(src.val[].counter) + if dest.val != nil: + `=destroy`(dest) + dest.val = src.val + +proc newSharedPtr*[T](val: sink Isolated[T]): SharedPtr[T] {.nodestroy.} = + ## Returns a shared pointer which shares + ## ownership of the object by reference counting. + result.val = cast[typeof(result.val)](allocShared(sizeof(result.val[]))) + int(result.val.counter) = 0 + result.val.value = extract val + +template newSharedPtr*[T](val: T): SharedPtr[T] = + newSharedPtr(isolate(val)) + +proc newSharedPtr*[T](t: typedesc[T]): SharedPtr[T] = + ## Returns a shared pointer. It is not initialized, + ## so reading from it before writing to it is undefined behaviour! + when not supportsCopyMem(T): + result.val = cast[typeof(result.val)](allocShared0(sizeof(T))) + else: + result.val = cast[typeof(result.val)](allocShared(sizeof(T))) + int(result.val.counter) = 0 + +proc isNil*[T](p: SharedPtr[T]): bool {.inline.} = + p.val == nil + +proc `[]`*[T](p: SharedPtr[T]): var T {.inline.} = + checkNotNil(p) + p.val.value + +proc `[]=`*[T](p: SharedPtr[T], val: sink Isolated[T]) {.inline.} = + checkNotNil(p) + p.val.value = extract val + +template `[]=`*[T](p: SharedPtr[T]; val: T) = + `[]=`(p, isolate(val)) + +proc `$`*[T](p: SharedPtr[T]): string {.inline.} = + if p.val == nil: "nil" + else: "(val: " & $p.val.value & ")" + +#------------------------------------------------------------------------------ + +type + ConstPtr*[T] = distinct SharedPtr[T] + ## Distinct version of `SharedPtr[T]`, which doesn't allow mutating the underlying value. + +proc newConstPtr*[T](val: sink Isolated[T]): ConstPtr[T] {.nodestroy, inline.} = + ## Similar to `newSharedPtr<#newSharedPtr,T>`_, but the underlying value can't be mutated. + ConstPtr[T](newSharedPtr(val)) + +template newConstPtr*[T](val: T): ConstPtr[T] = + newConstPtr(isolate(val)) + +proc isNil*[T](p: ConstPtr[T]): bool {.inline.} = + SharedPtr[T](p).val == nil + +proc `[]`*[T](p: ConstPtr[T]): lent T {.inline.} = + ## Returns an immutable view of the internal value of `p`. + checkNotNil(p) + SharedPtr[T](p).val.value + +proc `[]=`*[T](p: ConstPtr[T], v: T) = {.error: "`ConstPtr` cannot be assigned.".} + +proc `$`*[T](p: ConstPtr[T]): string {.inline.} = + $SharedPtr[T](p)