From e9bf17a04d08558bc7d6553a0a609f26f883e54d Mon Sep 17 00:00:00 2001 From: Stanislav Blinov Date: Sat, 15 Feb 2014 10:09:57 +0400 Subject: [PATCH 1/4] assumeUnshared: convert shared lvalue to a non-shared one --- changelog/core-atomic-assumeUnshared.dd | 23 +++++ src/core/atomic.d | 123 ++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 changelog/core-atomic-assumeUnshared.dd diff --git a/changelog/core-atomic-assumeUnshared.dd b/changelog/core-atomic-assumeUnshared.dd new file mode 100644 index 0000000000..0c4cc7d097 --- /dev/null +++ b/changelog/core-atomic-assumeUnshared.dd @@ -0,0 +1,23 @@ +`core.atomic.assumeUnshared` has been added + +$(REF assumeUnshared, core,atomic) allows to convert a shared lvalue to a non-shared lvalue + +--- +shared static int i; + +// i is never used outside of synchronized {} blocks... + +synchronized +{ + ++i; // ERROR: cannot directly modify shared lvalue + + atomicOp!"+="(i, 1); // possible overhead + + // Directly modify i + assumeUnshared(i) += 1; + // or: + ++assumeUnshared(i); + // or: + i.assumeUnshared += 1; +} +--- diff --git a/src/core/atomic.d b/src/core/atomic.d index e6a82e58f8..c5aae94003 100644 --- a/src/core/atomic.d +++ b/src/core/atomic.d @@ -263,6 +263,52 @@ in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") return cast(shared)core.internal.atomic.atomicExchange!ms(cast(T*)here, cast(V)exchangeWith); } +/** + * Converts a shared lvalue to a non-shared lvalue. + * + * This functions allows to treat a shared lvalue as if it was thread-local. + * It is useful to avoid overhead of atomic operations when access to shared data + * is known to be within one thread (i.e. always under a lock). + * --- + * shared static int i; + * + * // i is never used outside of synchronized {} blocks... + * + * synchronized + * { + * ++i; // ERROR: cannot directly modify shared lvalue + * + * atomicOp!"+="(i, 1); // possible overhead + * + * // Directly modify i + * assumeUnshared(i) += 1; + * // or: + * ++assumeUnshared(i); + * // or: + * i.assumeUnshared += 1; + * } + * --- + * Usage of this function is restricted to allowing limited lvalue access to shared instances of + * primitive and POD types (e.g. direct use of operators), thus it is not defined for classes. + * + * Note: this function does not perform any ordering. + * + * Note: assumeUnshared is a special-purpose primitive and should be used with care. When accessing + * shared variables both inside and outside of synchronized blocks, atomic operations should be + * used instead. + * + * Params: + * val = the shared lvalue. + * + * Returns: + * The non-shared lvalue. + */ +ref T assumeUnshared(T)(ref shared T val) @system @nogc pure nothrow + if(!is(T == class) && !is(T == interface)) +{ + return *cast(T*) &val; +} + /** * Performs either compare-and-set or compare-and-swap (or exchange). * @@ -1276,4 +1322,81 @@ version (CoreUnittest) shared NoIndirections n; static assert(is(typeof(atomicLoad(n)) == NoIndirections)); } + + pure nothrow @nogc @system unittest + { + int base = 0; + shared int atom = 0; + + // only accept shared lvalues + static assert(!is(typeof(assumeUnshared(base)))); + static assert(!is(typeof(assumeUnshared(cast(shared)base)))); + + ++assumeUnshared(atom); + assert(atomicLoad!(MemoryOrder.raw)(atom) == 1); + } + + pure nothrow @nogc @system unittest + { + shared const int catom = 0; + shared immutable int iatom = 0; + // allow const + static assert(is(typeof(assumeUnshared(catom)))); + static assert(is(typeof(assumeUnshared(iatom)))); + // preserve const + static assert(!is(typeof(++assumeUnshared(catom)))); + static assert(!is(typeof(++assumeUnshared(iatom)))); + } + + pure nothrow @nogc @system unittest + { + class Klass {} + + Klass c1; + shared Klass c2; + + // don't accept class instances + static assert(!is(typeof(assumeUnshared(c1)))); + static assert(!is(typeof(assumeUnshared(c2)))); + } + + pure nothrow @nogc @system unittest + { + interface Interface {} + Interface i1; + shared Interface i2; + + // don't accept interfaces + static assert(!is(typeof(assumeUnshared(i1)))); + static assert(!is(typeof(assumeUnshared(i2)))); + } + + pure nothrow @nogc @system unittest + { + // test assumeShared with inout + shared struct S + { + int atom = 0; + + @property ref get() inout + { + return atom.assumeUnshared; + } + } + + shared S sm; + shared const S sc; + shared immutable S si; + + static assert(is(typeof(sm.get) == int)); + static assert(is(typeof(sc.get) == const(int))); + static assert(is(typeof(si.get) == immutable(int))); + + static assert( is(typeof(++sm.get))); + static assert(!is(typeof(++sc.get))); + static assert(!is(typeof(++si.get))); + + sm.get += 10; + assert(atomicLoad!(MemoryOrder.raw)(sm.atom) == 10); + } } From 81bb4d6b4f2be23a1bbc77e75abf57ac2f4a50ae Mon Sep 17 00:00:00 2001 From: RazvanN7 Date: Tue, 9 Nov 2021 13:04:39 +0200 Subject: [PATCH 2/4] Address review feedback + rebase --- src/core/atomic.d | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/atomic.d b/src/core/atomic.d index c5aae94003..e8e48dbdf2 100644 --- a/src/core/atomic.d +++ b/src/core/atomic.d @@ -1330,7 +1330,6 @@ version (CoreUnittest) // only accept shared lvalues static assert(!is(typeof(assumeUnshared(base)))); - static assert(!is(typeof(assumeUnshared(cast(shared)base)))); ++assumeUnshared(atom); assert(atomicLoad!(MemoryOrder.raw)(atom) == 1); @@ -1342,7 +1341,6 @@ version (CoreUnittest) shared immutable int iatom = 0; // allow const static assert(is(typeof(assumeUnshared(catom)))); - static assert(is(typeof(assumeUnshared(iatom)))); // preserve const static assert(!is(typeof(++assumeUnshared(catom)))); static assert(!is(typeof(++assumeUnshared(iatom)))); From ea60beb2f17c0b23e7c31cae2cc92791da9f16ee Mon Sep 17 00:00:00 2001 From: RazvanN7 Date: Tue, 9 Nov 2021 13:06:29 +0200 Subject: [PATCH 3/4] Fix style checker --- src/core/atomic.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/atomic.d b/src/core/atomic.d index e8e48dbdf2..42b85c171e 100644 --- a/src/core/atomic.d +++ b/src/core/atomic.d @@ -304,7 +304,7 @@ in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") * The non-shared lvalue. */ ref T assumeUnshared(T)(ref shared T val) @system @nogc pure nothrow - if(!is(T == class) && !is(T == interface)) + if (!is(T == class) && !is(T == interface)) { return *cast(T*) &val; } From a229abaedc6cc0c73ef7f37c0f4852a85ef599fe Mon Sep 17 00:00:00 2001 From: RazvanN7 Date: Mon, 24 Jan 2022 15:24:57 +0200 Subject: [PATCH 4/4] Add note specifying that assumeUnshared is transitive --- src/core/atomic.d | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/atomic.d b/src/core/atomic.d index 42b85c171e..758faf3129 100644 --- a/src/core/atomic.d +++ b/src/core/atomic.d @@ -293,10 +293,13 @@ in (atomicPtrIsProperlyAligned(here), "Argument `here` is not properly aligned") * * Note: this function does not perform any ordering. * - * Note: assumeUnshared is a special-purpose primitive and should be used with care. When accessing + * Note: `assumeUnshared` is a special-purpose primitive and should be used with care. When accessing * shared variables both inside and outside of synchronized blocks, atomic operations should be * used instead. * + * Note: the result of assumeUnshared is an object for which `shared` has been stripped transitively. + * Therefore, any field that is accessed from the assumed unshared object will also be unshared. + * * Params: * val = the shared lvalue. *