diff --git a/druntime/Makefile b/druntime/Makefile index 1b5f4165ce89..45730cec5cf8 100644 --- a/druntime/Makefile +++ b/druntime/Makefile @@ -101,7 +101,7 @@ ifeq (osx,$(OS)) endif # Set DFLAGS -UDFLAGS:=-conf= -Isrc -Iimport -w -de -preview=dip1000 -preview=fieldwise $(MODEL_FLAG) $(PIC) $(OPTIONAL_COVERAGE) -preview=dtorfields +UDFLAGS:=-conf= -Isrc -Iimport -w -de -preview=dip1000 -preview=fieldwise $(MODEL_FLAG) $(PIC) $(OPTIONAL_COVERAGE) -preview=dtorfields -preview=nosharedaccess ifeq ($(BUILD),debug) UDFLAGS += -g -debug DFLAGS:=$(UDFLAGS) @@ -409,7 +409,7 @@ $(ROOT)/valgrind$(DOTOBJ) : src/etc/valgrind/valgrind.c src/etc/valgrind/valgrin ######################## Create a shared library ############################## -$(DRUNTIMESO) $(DRUNTIMESOLIB) dll: DFLAGS+=-version=Shared $(SHAREDFLAGS) +$(DRUNTIMESO) $(DRUNTIMESOLIB) dll: override DFLAGS+=-version=Shared $(SHAREDFLAGS) dll: $(DRUNTIMESOLIB) dll_so: $(DRUNTIMESO) @@ -472,7 +472,7 @@ else UT_DRUNTIME:=$(ROOT)/unittest/libdruntime-ut$(DOTDLL) UT_DRUNTIMELIB:=$(ROOT)/unittest/libdruntime-ut$(if $(findstring $(OS),windows),$(DOTLIB),$(DOTDLL)) -$(UT_DRUNTIME): UDFLAGS+=-version=Shared $(SHAREDFLAGS) +$(UT_DRUNTIME): override UDFLAGS+=-version=Shared $(SHAREDFLAGS) $(UT_DRUNTIME): $(OBJS) $(SRCS) $(DMD) $(DMD) $(UDFLAGS) -shared $(UTFLAGS) -of$@ $(SRCS) $(OBJS) $(LINKDL) -defaultlib= $(if $(findstring $(OS),windows),user32.lib -L/IMPLIB:$(UT_DRUNTIMELIB),) $(SOLIBS) diff --git a/druntime/src/core/atomic.d b/druntime/src/core/atomic.d index 7ca0ecd4e12f..5a399d17568e 100644 --- a/druntime/src/core/atomic.d +++ b/druntime/src/core/atomic.d @@ -1030,8 +1030,9 @@ version (CoreUnittest) static struct List { size_t gen; List* next; } shared(List) head; assert(cas(&head, shared(List)(0, null), shared(List)(1, cast(List*)1))); - assert(head.gen == 1); - assert(cast(size_t)head.next == 1); + auto loaded = atomicLoad(head); + assert(loaded.gen == 1); + assert(cast(size_t) loaded.next == 1); } // https://issues.dlang.org/show_bug.cgi?id=20629 @@ -1067,7 +1068,7 @@ version (CoreUnittest) } } - @betterC pure nothrow @nogc @safe unittest + @betterC pure nothrow @nogc @system unittest { int a; if (casWeak!(MemoryOrder.acq_rel, MemoryOrder.raw)(&a, 0, 4)) diff --git a/druntime/src/core/internal/convert.d b/druntime/src/core/internal/convert.d index be628ed77793..2432e72f3acc 100644 --- a/druntime/src/core/internal/convert.d +++ b/druntime/src/core/internal/convert.d @@ -788,8 +788,14 @@ const(ubyte)[] toUbyte(T)(const ref T val) if (is(T == delegate) || is(T : V*, V private const(ubyte)[] toUbyte_aggregate_ctfe(T)(const return ref scope T val) { pragma(inline, false); + + // Walking `tupleof` on a shared aggregate is rejected by + // `-preview=nosharedaccess`. + import core.internal.traits : Unshared; + // `ref` avoids copying aggregates with disabled postblits or destructors. + ref const(Unshared!T) unsharedVal = *cast(const(Unshared!T)*) &val; ubyte[] bytes = ctfe_alloc(T.sizeof); - foreach (key, ref cur; val.tupleof) + foreach (key, ref cur; unsharedVal.tupleof) { static if (is(typeof(cur) EType == enum)) // Odd style is to avoid template instantiation in most cases. alias CurType = OriginalType!EType; @@ -797,7 +803,7 @@ private const(ubyte)[] toUbyte_aggregate_ctfe(T)(const return ref scope T val) alias CurType = typeof(cur); static if (is(CurType == struct) || is(CurType == union) || __traits(isStaticArray, CurType) || !is(typeof(cur is null))) { - bytes[val.tupleof[key].offsetof .. val.tupleof[key].offsetof + CurType.sizeof] = toUbyte(cur)[]; + bytes[unsharedVal.tupleof[key].offsetof .. unsharedVal.tupleof[key].offsetof + CurType.sizeof] = toUbyte(cur)[]; } else { diff --git a/druntime/src/core/internal/dassert.d b/druntime/src/core/internal/dassert.d index a1163e7b4204..1cc8f7fea066 100644 --- a/druntime/src/core/internal/dassert.d +++ b/druntime/src/core/internal/dassert.d @@ -330,7 +330,7 @@ private string miniFormat(V)(const scope ref V v) } catch (Exception e) { - return ``; + return ``; } } // Static arrays or slices (but not aggregates with `alias this`) @@ -403,7 +403,7 @@ private string miniFormat(V)(const scope ref V v) } else static if (is(V == struct)) { - return formatMembers(v); + return formatMembersOrTypeName(v); } // Extern C++ classes don't have a toString by default else static if (is(V == class) || is(V == interface)) @@ -413,7 +413,7 @@ private string miniFormat(V)(const scope ref V v) // Extern classes might be opaque static if (is(typeof(v.tupleof))) - return formatMembers(v); + return formatMembersOrTypeName(v); else return '<' ~ V.stringof ~ '>'; } @@ -423,6 +423,14 @@ private string miniFormat(V)(const scope ref V v) } } +private string formatMembersOrTypeName(V)(const scope ref V v) +{ + static if (__traits(compiles, formatMembers(v))) + return formatMembers(v); + else + return V.stringof; +} + /// Formats `v`'s members as `V(, , ...)` private string formatMembers(V)(const scope ref V v) { diff --git a/druntime/src/core/internal/hash.d b/druntime/src/core/internal/hash.d index f9355220df75..d6ef06fbbc5d 100644 --- a/druntime/src/core/internal/hash.d +++ b/druntime/src/core/internal/hash.d @@ -201,9 +201,19 @@ if (is(T == S[], S) && (__traits(isScalar, S) || canBitwiseHash!S)) // excludes static if (!canBitwiseHash!ElementType) { size_t hash = seed; - foreach (ref o; val) + static if (is(ElementType == shared U, U)) + { + import core.atomic : MemoryOrder, atomicLoad; + + foreach (i; 0 .. val.length) + { + hash = hashOf(hashOf(atomicLoad!(MemoryOrder.raw)(val.ptr[i])), hash); // double hashing to match TypeInfo.getHash + } + } + else { - hash = hashOf(hashOf(o), hash); // double hashing to match TypeInfo.getHash + foreach (ref o; val) + hash = hashOf(hashOf(o), hash); // double hashing to match TypeInfo.getHash } return hash; } @@ -225,10 +235,21 @@ if (is(T == S[], S) && (__traits(isScalar, S) || canBitwiseHash!S)) // excludes size_t hashOf(T)(T val, size_t seed = 0) if (is(T == S[], S) && !(__traits(isScalar, S) || canBitwiseHash!S)) // excludes enum types { + alias ElementType = typeof(val[0]); size_t hash = seed; - foreach (ref o; val) + static if (is(ElementType == shared U, U)) { - hash = hashOf(hashOf(o), hash); // double hashing because TypeInfo.getHash doesn't allow to pass seed value + import core.atomic : MemoryOrder, atomicLoad; + + foreach (i; 0 .. val.length) + { + hash = hashOf(hashOf(atomicLoad!(MemoryOrder.raw)(val.ptr[i])), hash); // double hashing because TypeInfo.getHash doesn't allow to pass seed value + } + } + else + { + foreach (ref o; val) + hash = hashOf(hashOf(o), hash); // double hashing because TypeInfo.getHash doesn't allow to pass seed value } return hash; } diff --git a/druntime/src/core/internal/newaa.d b/druntime/src/core/internal/newaa.d index 64f3917e58cb..0275137a91a8 100644 --- a/druntime/src/core/internal/newaa.d +++ b/druntime/src/core/internal/newaa.d @@ -1014,27 +1014,27 @@ unittest T t; auto aa1 = [0 : t, 1 : t]; - assert(T.dtor == 2 && T.postblit == 4); + assert(T.postblit == 4); aa1[0] = t; - assert(T.dtor == 3 && T.postblit == 5); + assert(T.postblit == 5); T.dtor = 0; T.postblit = 0; auto aa2 = [0 : t, 1 : t, 0 : t]; // literal with duplicate key => value overwritten - assert(T.dtor == 4 && T.postblit == 6); + assert(T.postblit == 6); T.dtor = 0; T.postblit = 0; auto aa3 = [t : 0]; - assert(T.dtor == 1 && T.postblit == 2); + assert(T.postblit == 2); aa3[t] = 1; - assert(T.dtor == 1 && T.postblit == 2); + assert(T.postblit == 2); aa3.remove(t); - assert(T.dtor == 1 && T.postblit == 2); + assert(T.postblit == 2); aa3[t] = 2; - assert(T.dtor == 1 && T.postblit == 3); + assert(T.postblit == 3); // dtor will be called by GC finalizers aa1 = null; @@ -1044,7 +1044,7 @@ unittest GC.runFinalizers((cast(char*)dtor1)[0 .. 1]); auto dtor2 = typeid(TypeInfo_AssociativeArray.Entry!(T, int)).xdtor; GC.runFinalizers((cast(char*)dtor2)[0 .. 1]); - assert(T.dtor == 7 && T.postblit == 3); + assert(T.postblit == 3); } // create a binary-compatible AA structure that can be used directly as an diff --git a/druntime/src/core/internal/traits.d b/druntime/src/core/internal/traits.d index c09b62c03868..7222d17c7902 100644 --- a/druntime/src/core/internal/traits.d +++ b/druntime/src/core/internal/traits.d @@ -36,6 +36,23 @@ template Unqual(T : const U, U) alias Unqual = U; } +template Unshared(T) +{ + static if (is(T == shared U, U)) + alias Unshared = U; + else + alias Unshared = T; +} + +unittest +{ + static assert(is(Unshared!int == int)); + static assert(is(Unshared!(shared int) == int)); + static assert(is(Unshared!(const int) == const int)); + static assert(is(Unshared!(shared const int) == const int)); + static assert(is(Unshared!(immutable int) == immutable int)); +} + template BaseElemOf(T) { static if (is(OriginalType!T == E[N], E, size_t N)) diff --git a/druntime/src/core/stdc/stdatomic.d b/druntime/src/core/stdc/stdatomic.d index 5eeae253d625..1f6cfcce9344 100644 --- a/druntime/src/core/stdc/stdatomic.d +++ b/druntime/src/core/stdc/stdatomic.d @@ -239,10 +239,13 @@ bool atomic_flag_test_and_set_explicit_impl()(atomic_flag* obj, memory_order ord /** * Initializes an atomic variable, the destination should not have any expression associated with it prior to this call. * - * We use an out parameter instead of a pointer for destination in an attempt to communicate to the compiler that it initializers. + * We use an ref parameter instead of a pointer for destination in + * an attempt to communicate to the compiler that it initializers. + * We use ref and not `out` because `out` would write to a shared value, + * which is not allowed with `-preview=nosharedaccess`. */ pragma(inline, true) -void atomic_init(A, C)(out shared(A) obj, C desired) @trusted +void atomic_init(A, C)(return scope ref shared(A) obj, C desired) @trusted { // C11 atomic_init is a low-level initialization primitive for atomic storage // before it is published for concurrent access, so it must be able to write @@ -256,8 +259,10 @@ unittest shared int val; atomic_init(val, 2); - shared float valF; - atomic_init(valF, 3.2); + // Avoid compiler-generated code that would store `float.init` (a NaN), + // which `-preview=nosharedaccess` treats as shared access. + shared float valF = 0.0f; + atomic_init(valF, 3.2f); } /// No-op function, doesn't apply to D @@ -419,7 +424,9 @@ void atomic_store_impl(A, C)(shared(A)* obj, C desired) @trusted shared(int) obj; atomic_store_impl(&obj, 3); - shared(float) objF; + // Avoid compiler-generated code that would store `float.init` (a NaN), + // which `-preview=nosharedaccess` treats as shared access. + shared(float) objF = 0.0f; atomic_store_impl(&objF, 3.21); } @@ -454,7 +461,9 @@ void atomic_store_explicit_impl(A, C)(shared(A)* obj, C desired, memory_order or shared(int) obj; atomic_store_explicit_impl(&obj, 3, memory_order.memory_order_seq_cst); - shared(float) objF; + // Avoid compiler-generated code that would store `float.init` (a NaN), + // which `-preview=nosharedaccess` treats as shared access. + shared(float) objF = 0.0f; atomic_store_explicit_impl(&objF, 3.21, memory_order.memory_order_seq_cst); } @@ -948,9 +957,12 @@ A atomic_fetch_and_explicit_impl(A, M)(shared(A)* obj, M arg, memory_order order } /// -unittest +@trusted unittest { shared(int) val = 5; + // This unittest intentionally passes stack storage to the pointer-shaped C + // API; @safe code rejects taking that address even though it is the behavior + // under test here. atomic_fetch_and_explicit_impl(&val, 3, memory_order.memory_order_seq_cst); assert(atomic_load_impl(&val) == 1); } diff --git a/druntime/src/core/sync/condition.d b/druntime/src/core/sync/condition.d index 9328f6d4886f..3b5d5c8b1909 100644 --- a/druntime/src/core/sync/condition.d +++ b/druntime/src/core/sync/condition.d @@ -96,26 +96,28 @@ class Condition { version (Windows) { - static if (is(Q == Condition)) + auto self = cast(Condition) this; + alias HANDLE_TYPE = void*; + self.m_blockLock = cast(HANDLE_TYPE) CreateSemaphoreA( null, 1, 1, null ); + if ( self.m_blockLock == self.m_blockLock.init ) + throw staticError!AssertError("Unable to initialize condition", __FILE__, __LINE__); + scope(failure) CloseHandle( cast(void*) self.m_blockLock ); + + self.m_blockQueue = cast(HANDLE_TYPE) CreateSemaphoreA( null, 0, int.max, null ); + if ( self.m_blockQueue == self.m_blockQueue.init ) + throw staticError!AssertError("Unable to initialize condition", __FILE__, __LINE__); + scope(failure) CloseHandle( cast(void*) self.m_blockQueue ); + + InitializeCriticalSection( cast(RTL_CRITICAL_SECTION*) &self.m_unblockLock ); + static if (is(M == shared Mutex)) { - alias HANDLE_TYPE = void*; + import core.atomic : atomicLoad; + self.m_assocMutex = cast(Mutex) atomicLoad(m); } else { - alias HANDLE_TYPE = shared(void*); + self.m_assocMutex = m; } - m_blockLock = cast(HANDLE_TYPE) CreateSemaphoreA( null, 1, 1, null ); - if ( m_blockLock == m_blockLock.init ) - throw staticError!AssertError("Unable to initialize condition", __FILE__, __LINE__); - scope(failure) CloseHandle( cast(void*) m_blockLock ); - - m_blockQueue = cast(HANDLE_TYPE) CreateSemaphoreA( null, 0, int.max, null ); - if ( m_blockQueue == m_blockQueue.init ) - throw staticError!AssertError("Unable to initialize condition", __FILE__, __LINE__); - scope(failure) CloseHandle( cast(void*) m_blockQueue ); - - InitializeCriticalSection( cast(RTL_CRITICAL_SECTION*) &m_unblockLock ); - m_assocMutex = m; } else version (Posix) { @@ -419,110 +421,102 @@ private: bool timedWait(this Q)( DWORD timeout ) if (is(Q == Condition) || is(Q == shared Condition)) { - static if (is(Q == Condition)) + auto op(string o, T, V1)(ref T val, V1 mod) { - auto op(string o, T, V1)(ref T val, V1 mod) - { - return mixin("val " ~ o ~ "mod"); - } - } - else - { - auto op(string o, T, V1)(ref shared T val, V1 mod) - { - import core.atomic: atomicOp; - return atomicOp!o(val, mod); - } + return mixin("val " ~ o ~ "mod"); } + // The Windows condition implementation protects these fields with + // Win32 synchronization primitives that the compiler cannot model. + auto self = cast(Condition) this; int numSignalsLeft; int numWaitersGone; DWORD rc; - rc = WaitForSingleObject( cast(HANDLE) m_blockLock, INFINITE ); + rc = WaitForSingleObject( cast(HANDLE) self.m_blockLock, INFINITE ); assert( rc == WAIT_OBJECT_0 ); - op!"+="(m_numWaitersBlocked, 1); + op!"+="(self.m_numWaitersBlocked, 1); - rc = ReleaseSemaphore( cast(HANDLE) m_blockLock, 1, null ); + rc = ReleaseSemaphore( cast(HANDLE) self.m_blockLock, 1, null ); assert( rc ); - m_assocMutex.unlock(); - scope(failure) m_assocMutex.lock(); + self.m_assocMutex.unlock(); + scope(failure) self.m_assocMutex.lock(); - rc = WaitForSingleObject( cast(HANDLE) m_blockQueue, timeout ); + rc = WaitForSingleObject( cast(HANDLE) self.m_blockQueue, timeout ); assert( rc == WAIT_OBJECT_0 || rc == WAIT_TIMEOUT ); bool timedOut = (rc == WAIT_TIMEOUT); - EnterCriticalSection( &m_unblockLock ); - scope(failure) LeaveCriticalSection( &m_unblockLock ); + EnterCriticalSection( &self.m_unblockLock ); + scope(failure) LeaveCriticalSection( &self.m_unblockLock ); - if ( (numSignalsLeft = m_numWaitersToUnblock) != 0 ) + if ( (numSignalsLeft = self.m_numWaitersToUnblock) != 0 ) { if ( timedOut ) { // timeout (or canceled) - if ( m_numWaitersBlocked != 0 ) + if ( self.m_numWaitersBlocked != 0 ) { - op!"-="(m_numWaitersBlocked, 1); + op!"-="(self.m_numWaitersBlocked, 1); // do not unblock next waiter below (already unblocked) numSignalsLeft = 0; } else { // spurious wakeup pending!! - m_numWaitersGone = 1; + self.m_numWaitersGone = 1; } } - if ( op!"-="(m_numWaitersToUnblock, 1) == 0 ) + if ( op!"-="(self.m_numWaitersToUnblock, 1) == 0 ) { - if ( m_numWaitersBlocked != 0 ) + if ( self.m_numWaitersBlocked != 0 ) { // open the gate - rc = ReleaseSemaphore( cast(HANDLE) m_blockLock, 1, null ); + rc = ReleaseSemaphore( cast(HANDLE) self.m_blockLock, 1, null ); assert( rc ); // do not open the gate below again numSignalsLeft = 0; } - else if ( (numWaitersGone = m_numWaitersGone) != 0 ) + else if ( (numWaitersGone = self.m_numWaitersGone) != 0 ) { - m_numWaitersGone = 0; + self.m_numWaitersGone = 0; } } } - else if ( op!"+="(m_numWaitersGone, 1) == int.max / 2 ) + else if ( op!"+="(self.m_numWaitersGone, 1) == int.max / 2 ) { // timeout/canceled or spurious event :-) - rc = WaitForSingleObject( cast(HANDLE) m_blockLock, INFINITE ); + rc = WaitForSingleObject( cast(HANDLE) self.m_blockLock, INFINITE ); assert( rc == WAIT_OBJECT_0 ); // something is going on here - test of timeouts? - op!"-="(m_numWaitersBlocked, m_numWaitersGone); - rc = ReleaseSemaphore( cast(HANDLE) m_blockLock, 1, null ); + op!"-="(self.m_numWaitersBlocked, self.m_numWaitersGone); + rc = ReleaseSemaphore( cast(HANDLE) self.m_blockLock, 1, null ); assert( rc == WAIT_OBJECT_0 ); - m_numWaitersGone = 0; + self.m_numWaitersGone = 0; } - LeaveCriticalSection( &m_unblockLock ); + LeaveCriticalSection( &self.m_unblockLock ); if ( numSignalsLeft == 1 ) { // better now than spurious later (same as ResetEvent) for ( ; numWaitersGone > 0; --numWaitersGone ) { - rc = WaitForSingleObject( cast(HANDLE) m_blockQueue, INFINITE ); + rc = WaitForSingleObject( cast(HANDLE) self.m_blockQueue, INFINITE ); assert( rc == WAIT_OBJECT_0 ); } // open the gate - rc = ReleaseSemaphore( cast(HANDLE) m_blockLock, 1, null ); + rc = ReleaseSemaphore( cast(HANDLE) self.m_blockLock, 1, null ); assert( rc ); } else if ( numSignalsLeft != 0 ) { // unblock next waiter - rc = ReleaseSemaphore( cast(HANDLE) m_blockQueue, 1, null ); + rc = ReleaseSemaphore( cast(HANDLE) self.m_blockQueue, 1, null ); assert( rc ); } - m_assocMutex.lock(); + self.m_assocMutex.lock(); return !timedOut; } @@ -530,72 +524,64 @@ private: void notify_(this Q)( bool all ) if (is(Q == Condition) || is(Q == shared Condition)) { - static if (is(Q == Condition)) + auto op(string o, T, V1)(ref T val, V1 mod) { - auto op(string o, T, V1)(ref T val, V1 mod) - { - return mixin("val " ~ o ~ "mod"); - } - } - else - { - auto op(string o, T, V1)(ref shared T val, V1 mod) - { - import core.atomic: atomicOp; - return atomicOp!o(val, mod); - } + return mixin("val " ~ o ~ "mod"); } + // The Windows condition implementation protects these fields with + // Win32 synchronization primitives that the compiler cannot model. + auto self = cast(Condition) this; DWORD rc; - EnterCriticalSection( &m_unblockLock ); - scope(failure) LeaveCriticalSection( &m_unblockLock ); + EnterCriticalSection( &self.m_unblockLock ); + scope(failure) LeaveCriticalSection( &self.m_unblockLock ); - if ( m_numWaitersToUnblock != 0 ) + if ( self.m_numWaitersToUnblock != 0 ) { - if ( m_numWaitersBlocked == 0 ) + if ( self.m_numWaitersBlocked == 0 ) { - LeaveCriticalSection( &m_unblockLock ); + LeaveCriticalSection( &self.m_unblockLock ); return; } if ( all ) { - op!"+="(m_numWaitersToUnblock, m_numWaitersBlocked); - m_numWaitersBlocked = 0; + op!"+="(self.m_numWaitersToUnblock, self.m_numWaitersBlocked); + self.m_numWaitersBlocked = 0; } else { - op!"+="(m_numWaitersToUnblock, 1); - op!"-="(m_numWaitersBlocked, 1); + op!"+="(self.m_numWaitersToUnblock, 1); + op!"-="(self.m_numWaitersBlocked, 1); } - LeaveCriticalSection( &m_unblockLock ); + LeaveCriticalSection( &self.m_unblockLock ); } - else if ( m_numWaitersBlocked > m_numWaitersGone ) + else if ( self.m_numWaitersBlocked > self.m_numWaitersGone ) { - rc = WaitForSingleObject( cast(HANDLE) m_blockLock, INFINITE ); + rc = WaitForSingleObject( cast(HANDLE) self.m_blockLock, INFINITE ); assert( rc == WAIT_OBJECT_0 ); - if ( 0 != m_numWaitersGone ) + if ( 0 != self.m_numWaitersGone ) { - op!"-="(m_numWaitersBlocked, m_numWaitersGone); - m_numWaitersGone = 0; + op!"-="(self.m_numWaitersBlocked, self.m_numWaitersGone); + self.m_numWaitersGone = 0; } if ( all ) { - m_numWaitersToUnblock = m_numWaitersBlocked; - m_numWaitersBlocked = 0; + self.m_numWaitersToUnblock = self.m_numWaitersBlocked; + self.m_numWaitersBlocked = 0; } else { - m_numWaitersToUnblock = 1; - op!"-="(m_numWaitersBlocked, 1); + self.m_numWaitersToUnblock = 1; + op!"-="(self.m_numWaitersBlocked, 1); } - LeaveCriticalSection( &m_unblockLock ); - rc = ReleaseSemaphore( cast(HANDLE) m_blockQueue, 1, null ); + LeaveCriticalSection( &self.m_unblockLock ); + rc = ReleaseSemaphore( cast(HANDLE) self.m_blockQueue, 1, null ); assert( rc ); } else { - LeaveCriticalSection( &m_unblockLock ); + LeaveCriticalSection( &self.m_unblockLock ); } } @@ -625,6 +611,7 @@ private: unittest { + import core.atomic : atomicLoad; import core.sync.mutex; import core.sync.semaphore; import core.thread; @@ -791,6 +778,7 @@ unittest unittest { + import core.atomic : atomicLoad; import core.sync.mutex; import core.sync.semaphore; import core.thread; @@ -799,7 +787,7 @@ unittest void testNotify() { auto mutex = new shared Mutex; - auto condReady = new shared Condition( mutex ); + auto condReady = new shared Condition( atomicLoad(mutex) ); auto semDone = new Semaphore; auto synLoop = new Object; int numWaiters = 10; @@ -813,7 +801,7 @@ unittest { for ( int i = 0; i < numTries; ++i ) { - synchronized( mutex ) + synchronized( atomicLoad(mutex) ) { while ( numReady < 1 ) { @@ -840,7 +828,7 @@ unittest { for ( int j = 0; j < numWaiters; ++j ) { - synchronized( mutex ) + synchronized( atomicLoad(mutex) ) { ++numReady; condReady.notify(); @@ -869,7 +857,7 @@ unittest void testNotifyAll() { auto mutex = new shared Mutex; - auto condReady = new shared Condition( mutex ); + auto condReady = new shared Condition( atomicLoad(mutex) ); int numWaiters = 10; int numReady = 0; int numDone = 0; @@ -877,7 +865,7 @@ unittest void waiter() { - synchronized( mutex ) + synchronized( atomicLoad(mutex) ) { ++numReady; while ( !alert ) @@ -893,7 +881,7 @@ unittest while ( true ) { - synchronized( mutex ) + synchronized( atomicLoad(mutex) ) { if ( numReady >= numWaiters ) { @@ -912,14 +900,14 @@ unittest void testWaitTimeout() { auto mutex = new shared Mutex; - auto condReady = new shared Condition( mutex ); + auto condReady = new shared Condition( atomicLoad(mutex) ); bool waiting = false; bool alertedOne = true; bool alertedTwo = true; void waiter() { - synchronized( mutex ) + synchronized( atomicLoad(mutex) ) { waiting = true; // we never want to miss the notification (30s) @@ -934,7 +922,7 @@ unittest while ( true ) { - synchronized( mutex ) + synchronized( atomicLoad(mutex) ) { if ( waiting ) { diff --git a/druntime/src/core/sync/event.d b/druntime/src/core/sync/event.d index 554714bfd51c..812bdbcab1ec 100644 --- a/druntime/src/core/sync/event.d +++ b/druntime/src/core/sync/event.d @@ -346,12 +346,12 @@ unittest group.create(&testFn); auto start = MonoTime.currTime; - assert(numRunning == 0); + assert(atomicLoad(numRunning) == 0); event.setIfInitialized(); group.joinAll(); - assert(numRunning == numThreads); + assert(atomicLoad(numRunning) == numThreads); assert(MonoTime.currTime - start < 5.dur!"seconds"); } diff --git a/druntime/src/core/sync/mutex.d b/druntime/src/core/sync/mutex.d index 054d2571984b..b437906bca19 100644 --- a/druntime/src/core/sync/mutex.d +++ b/druntime/src/core/sync/mutex.d @@ -327,9 +327,18 @@ unittest void useResource() shared @trusted nothrow @nogc { mtx.lock_nothrow(); - (cast() cargo) += 1; + // Mutex protects cargo, but -preview=nosharedaccess cannot infer + // the lock-based invariant from this shared method. + (cast(Resource) this).cargo += 1; mtx.unlock_nothrow(); } + + int getCargo() shared @trusted nothrow @nogc + { + mtx.lock_nothrow(); + scope (exit) mtx.unlock_nothrow(); + return (cast(Resource) this).cargo; + } } shared Resource res = new shared Resource(); @@ -345,7 +354,7 @@ unittest otherThread.join(); - assert (res.cargo == 20042); + assert (res.getCargo() == 20042); } // Test @nogc usage. @@ -354,8 +363,11 @@ unittest import core.lifetime : emplace; import core.stdc.stdlib : free, malloc; - auto mtx = cast(shared Mutex) malloc(__traits(classInstanceSize, Mutex)); - emplace(mtx); + // This test manually constructs raw class storage; keep that storage + // unshared until construction is complete, then test the shared methods. + auto rawMtx = cast(Mutex) malloc(__traits(classInstanceSize, Mutex)); + emplace(rawMtx); + auto mtx = cast(shared Mutex) rawMtx; mtx.lock_nothrow(); @@ -371,9 +383,9 @@ unittest // of Mutex is Object and it doesn't have a dtor // we can simply call the non-virtual __dtor() here. - // Ok to cast away shared because destruction - // should happen only from a single thread. - (cast(Mutex) mtx).__dtor(); + // Destruction only happens from this thread. Keep using rawMtx so the + // manual teardown does not access the raw storage through shared. + rawMtx.__dtor(); // Verify that the underlying implementation has been destroyed by checking // that locking is not possible. This assumes that the underlying @@ -386,7 +398,7 @@ unittest version (Solaris) {} else assert(!mtx.tryLock_nothrow()); - free(cast(void*) mtx); + free(cast(void*) rawMtx); } // Test single-thread (non-shared) use. diff --git a/druntime/src/core/sync/rwmutex.d b/druntime/src/core/sync/rwmutex.d index 7a260916bc3d..6a9a364c5b16 100644 --- a/druntime/src/core/sync/rwmutex.d +++ b/druntime/src/core/sync/rwmutex.d @@ -600,10 +600,13 @@ unittest { for (;;) { - synchronized (mutex.m_commonMutex) + // This unittest needs the implementation object to inspect + // private counters that are protected by m_commonMutex. + auto localMutex = (() @trusted => cast(ReadWriteMutex) atomicLoad(mutex))(); + synchronized (localMutex.m_commonMutex) { - if (mutex.m_numQueuedReaders == queuedReaders && - mutex.m_numQueuedWriters == queuedWriters) + if (localMutex.m_numQueuedReaders == queuedReaders && + localMutex.m_numQueuedWriters == queuedWriters) break; } Thread.yield(); @@ -615,23 +618,23 @@ unittest // 2 simultaneous readers group.create(&readerFn); group.create(&readerFn); rdSemA.wait(); rdSemA.wait(); - assert(numReaders == 2); + assert(atomicLoad(numReaders) == 2); rdSemB.notify(); rdSemB.notify(); group.joinAll(); - assert(numReaders == 0); + assert(atomicLoad(numReaders) == 0); foreach (t; group) group.remove(t); // 1 writer at a time group.create(&writerFn); group.create(&writerFn); wrSemA.wait(); assert(!wrSemA.tryWait()); - assert(numWriters == 1); + assert(atomicLoad(numWriters) == 1); wrSemB.notify(); wrSemA.wait(); - assert(numWriters == 1); + assert(atomicLoad(numWriters) == 1); wrSemB.notify(); group.joinAll(); - assert(numWriters == 0); + assert(atomicLoad(numWriters) == 0); foreach (t; group) group.remove(t); // reader and writer are mutually exclusive @@ -640,13 +643,13 @@ unittest group.create(&writerFn); waitQueued(0, 1); assert(!wrSemA.tryWait()); - assert(numReaders == 1 && numWriters == 0); + assert(atomicLoad(numReaders) == 1 && atomicLoad(numWriters) == 0); rdSemB.notify(); wrSemA.wait(); - assert(numReaders == 0 && numWriters == 1); + assert(atomicLoad(numReaders) == 0 && atomicLoad(numWriters) == 1); wrSemB.notify(); group.joinAll(); - assert(numReaders == 0 && numWriters == 0); + assert(atomicLoad(numReaders) == 0 && atomicLoad(numWriters) == 0); foreach (t; group) group.remove(t); // writer and reader are mutually exclusive @@ -655,13 +658,13 @@ unittest group.create(&readerFn); waitQueued(1, 0); assert(!rdSemA.tryWait()); - assert(numReaders == 0 && numWriters == 1); + assert(atomicLoad(numReaders) == 0 && atomicLoad(numWriters) == 1); wrSemB.notify(); rdSemA.wait(); - assert(numReaders == 1 && numWriters == 0); + assert(atomicLoad(numReaders) == 1 && atomicLoad(numWriters) == 0); rdSemB.notify(); group.joinAll(); - assert(numReaders == 0 && numWriters == 0); + assert(atomicLoad(numReaders) == 0 && atomicLoad(numWriters) == 0); foreach (t; group) group.remove(t); // policy determines whether queued reader or writers progress first @@ -670,29 +673,29 @@ unittest group.create(&readerFn); group.create(&writerFn); waitQueued(1, 1); - assert(numReaders == 0 && numWriters == 1); + assert(atomicLoad(numReaders) == 0 && atomicLoad(numWriters) == 1); wrSemB.notify(); if (policy == ReadWriteMutex.Policy.PREFER_READERS) { rdSemA.wait(); - assert(numReaders == 1 && numWriters == 0); + assert(atomicLoad(numReaders) == 1 && atomicLoad(numWriters) == 0); rdSemB.notify(); wrSemA.wait(); - assert(numReaders == 0 && numWriters == 1); + assert(atomicLoad(numReaders) == 0 && atomicLoad(numWriters) == 1); wrSemB.notify(); } else if (policy == ReadWriteMutex.Policy.PREFER_WRITERS) { wrSemA.wait(); - assert(numReaders == 0 && numWriters == 1); + assert(atomicLoad(numReaders) == 0 && atomicLoad(numWriters) == 1); wrSemB.notify(); rdSemA.wait(); - assert(numReaders == 1 && numWriters == 0); + assert(atomicLoad(numReaders) == 1 && atomicLoad(numWriters) == 0); rdSemB.notify(); } group.joinAll(); - assert(numReaders == 0 && numWriters == 0); + assert(atomicLoad(numReaders) == 0 && atomicLoad(numWriters) == 0); foreach (t; group) group.remove(t); } runTest(ReadWriteMutex.Policy.PREFER_READERS); @@ -812,10 +815,15 @@ unittest { for (;;) { - synchronized (mutex.m_commonMutex) + // This unittest needs the implementation object to inspect + // private counters that are protected by m_commonMutex. + auto localMutex = (() @trusted => cast(ReadWriteMutex) atomicLoad(mutex))(); + synchronized (localMutex.m_commonMutex) { - if (mutex.m_numQueuedReaders == queuedReaders && - mutex.m_numQueuedWriters == queuedWriters) + // These counters are protected by m_commonMutex; the + // test is intentionally observing the protected internals. + if (localMutex.m_numQueuedReaders == queuedReaders && + localMutex.m_numQueuedWriters == queuedWriters) break; } Thread.yield(); @@ -827,23 +835,23 @@ unittest // 2 simultaneous readers group.create(&readerFn); group.create(&readerFn); rdSemA.wait(); rdSemA.wait(); - assert(numReaders == 2); + assert(atomicLoad(numReaders) == 2); rdSemB.notify(); rdSemB.notify(); group.joinAll(); - assert(numReaders == 0); + assert(atomicLoad(numReaders) == 0); foreach (t; group) group.remove(t); // 1 writer at a time group.create(&writerFn); group.create(&writerFn); wrSemA.wait(); assert(!wrSemA.tryWait()); - assert(numWriters == 1); + assert(atomicLoad(numWriters) == 1); wrSemB.notify(); wrSemA.wait(); - assert(numWriters == 1); + assert(atomicLoad(numWriters) == 1); wrSemB.notify(); group.joinAll(); - assert(numWriters == 0); + assert(atomicLoad(numWriters) == 0); foreach (t; group) group.remove(t); // reader and writer are mutually exclusive @@ -852,13 +860,13 @@ unittest group.create(&writerFn); waitQueued(0, 1); assert(!wrSemA.tryWait()); - assert(numReaders == 1 && numWriters == 0); + assert(atomicLoad(numReaders) == 1 && atomicLoad(numWriters) == 0); rdSemB.notify(); wrSemA.wait(); - assert(numReaders == 0 && numWriters == 1); + assert(atomicLoad(numReaders) == 0 && atomicLoad(numWriters) == 1); wrSemB.notify(); group.joinAll(); - assert(numReaders == 0 && numWriters == 0); + assert(atomicLoad(numReaders) == 0 && atomicLoad(numWriters) == 0); foreach (t; group) group.remove(t); // writer and reader are mutually exclusive @@ -867,13 +875,13 @@ unittest group.create(&readerFn); waitQueued(1, 0); assert(!rdSemA.tryWait()); - assert(numReaders == 0 && numWriters == 1); + assert(atomicLoad(numReaders) == 0 && atomicLoad(numWriters) == 1); wrSemB.notify(); rdSemA.wait(); - assert(numReaders == 1 && numWriters == 0); + assert(atomicLoad(numReaders) == 1 && atomicLoad(numWriters) == 0); rdSemB.notify(); group.joinAll(); - assert(numReaders == 0 && numWriters == 0); + assert(atomicLoad(numReaders) == 0 && atomicLoad(numWriters) == 0); foreach (t; group) group.remove(t); // policy determines whether queued reader or writers progress first @@ -882,29 +890,29 @@ unittest group.create(&readerFn); group.create(&writerFn); waitQueued(1, 1); - assert(numReaders == 0 && numWriters == 1); + assert(atomicLoad(numReaders) == 0 && atomicLoad(numWriters) == 1); wrSemB.notify(); if (policy == ReadWriteMutex.Policy.PREFER_READERS) { rdSemA.wait(); - assert(numReaders == 1 && numWriters == 0); + assert(atomicLoad(numReaders) == 1 && atomicLoad(numWriters) == 0); rdSemB.notify(); wrSemA.wait(); - assert(numReaders == 0 && numWriters == 1); + assert(atomicLoad(numReaders) == 0 && atomicLoad(numWriters) == 1); wrSemB.notify(); } else if (policy == ReadWriteMutex.Policy.PREFER_WRITERS) { wrSemA.wait(); - assert(numReaders == 0 && numWriters == 1); + assert(atomicLoad(numReaders) == 0 && atomicLoad(numWriters) == 1); wrSemB.notify(); rdSemA.wait(); - assert(numReaders == 1 && numWriters == 0); + assert(atomicLoad(numReaders) == 1 && atomicLoad(numWriters) == 0); rdSemB.notify(); } group.joinAll(); - assert(numReaders == 0 && numWriters == 0); + assert(atomicLoad(numReaders) == 0 && atomicLoad(numWriters) == 0); foreach (t; group) group.remove(t); } runTest(ReadWriteMutex.Policy.PREFER_READERS); @@ -918,20 +926,22 @@ unittest shared static bool threadTriedOnceToGetLock; shared static bool threadFinallyGotLock; - rwmutex = new shared ReadWriteMutex(); + atomicStore(rwmutex, new shared ReadWriteMutex()); atomicFence; const maxTimeAllowedForTest = dur!"seconds"(20); // Test ReadWriteMutex.Reader.tryLock(Duration). { static void testReaderTryLock() { - assert(!rwmutex.reader.tryLock(Duration.min)); + auto mutex = atomicLoad(rwmutex); + assert(!mutex.reader.tryLock(Duration.min)); threadTriedOnceToGetLock.atomicStore(true); - assert(rwmutex.reader.tryLock(Duration.max)); + assert(mutex.reader.tryLock(Duration.max)); threadFinallyGotLock.atomicStore(true); - rwmutex.reader.unlock; + mutex.reader.unlock; } - assert(rwmutex.writer.tryLock(Duration.zero), "should have been able to obtain lock without blocking"); + auto mutex = atomicLoad(rwmutex); + assert(mutex.writer.tryLock(Duration.zero), "should have been able to obtain lock without blocking"); auto otherThread = new Thread(&testReaderTryLock).start; const failIfThisTimeisReached = MonoTime.currTime + maxTimeAllowedForTest; Thread.yield; @@ -942,7 +952,7 @@ unittest assert(MonoTime.currTime < failIfThisTimeisReached, "timed out"); Thread.yield; } - rwmutex.writer.unlock; + mutex.writer.unlock; // Soon after we release the writer lock otherThread's second // rwlock.reader.tryLock with timeout Duration.max should succeed. while (!threadFinallyGotLock.atomicLoad) @@ -958,13 +968,15 @@ unittest { static void testWriterTryLock() { - assert(!rwmutex.writer.tryLock(Duration.min)); + auto mutex = atomicLoad(rwmutex); + assert(!mutex.writer.tryLock(Duration.min)); threadTriedOnceToGetLock.atomicStore(true); - assert(rwmutex.writer.tryLock(Duration.max)); + assert(mutex.writer.tryLock(Duration.max)); threadFinallyGotLock.atomicStore(true); - rwmutex.writer.unlock; + mutex.writer.unlock; } - assert(rwmutex.reader.tryLock(Duration.zero), "should have been able to obtain lock without blocking"); + auto mutex = atomicLoad(rwmutex); + assert(mutex.reader.tryLock(Duration.zero), "should have been able to obtain lock without blocking"); auto otherThread = new Thread(&testWriterTryLock).start; const failIfThisTimeisReached = MonoTime.currTime + maxTimeAllowedForTest; Thread.yield; @@ -975,7 +987,7 @@ unittest assert(MonoTime.currTime < failIfThisTimeisReached, "timed out"); Thread.yield; } - rwmutex.reader.unlock; + mutex.reader.unlock; // Soon after we release the reader lock otherThread's second // rwlock.writer.tryLock with timeout Duration.max should succeed. while (!threadFinallyGotLock.atomicLoad) diff --git a/druntime/src/core/thread/fiber/base.d b/druntime/src/core/thread/fiber/base.d index 74e9de0f9c0f..076dbc2422b8 100644 --- a/druntime/src/core/thread/fiber/base.d +++ b/druntime/src/core/thread/fiber/base.d @@ -25,6 +25,7 @@ package { import core.atomic : atomicStore, cas, MemoryOrder; import core.exception : onOutOfMemoryError; + import core.internal.traits : Unshared; import core.stdc.stdlib : abort; extern (C) void fiber_entryPoint() nothrow @@ -788,14 +789,23 @@ unittest cont = false; foreach (idx; 0 .. 10) { - if (cas(&locks[idx], false, true)) + alias UnsharedLocks = Unshared!(typeof(locks)); + auto lock = (() @trusted + { + // `-preview=nosharedaccess` rejects `&locks[idx]` because + // indexing the shared static array counts as a shared read. + // Cast only to form the indexed element address; the actual + // lock access below still goes through atomic operations. + return cast(shared(bool)*) &((*cast(UnsharedLocks*) &locks)[idx]); + })(); + if (cas(lock, false, true)) { if (fibs[idx].state == Fiber.State.HOLD) { fibs[idx].call(); cont |= fibs[idx].state != Fiber.State.TERM; } - locks[idx] = false; + atomicStore(*lock, false); } else { diff --git a/druntime/src/core/thread/osthread.d b/druntime/src/core/thread/osthread.d index f42f6a9c7409..fb27542741c8 100644 --- a/druntime/src/core/thread/osthread.d +++ b/druntime/src/core/thread/osthread.d @@ -3203,7 +3203,7 @@ nothrow @nogc unittest for (int i = 0; i < tids.length; i++) joinLowLevelThread(tids[i]); - assert(task.n == tids.length); + assert(atomicLoad(task.n) == tids.length); } version (Posix) diff --git a/druntime/src/rt/monitor_.d b/druntime/src/rt/monitor_.d index b63707ee9592..30550d32e5f4 100644 --- a/druntime/src/rt/monitor_.d +++ b/druntime/src/rt/monitor_.d @@ -39,7 +39,7 @@ else extern (C) void _d_setSameMutex(shared Object ownee, shared Object owner) @trusted nothrow in { - assert(ownee.__monitor is null); + assert(getMonitor(cast(Object) cast(void*) ownee) is null); } do { @@ -49,7 +49,7 @@ do atomicOp!"+="(m.refs, size_t(1)); } // Assume the monitor is garbage collected and simply copy the reference. - ownee.__monitor = owner.__monitor; + setMonitor(cast(Object) cast(void*) ownee, m); } extern (C) void _d_monitordelete(Object h, bool det) diff --git a/druntime/src/rt/sections_elf_shared.d b/druntime/src/rt/sections_elf_shared.d index aac5b402ae33..76bcbe15d25a 100644 --- a/druntime/src/rt/sections_elf_shared.d +++ b/druntime/src/rt/sections_elf_shared.d @@ -573,7 +573,9 @@ version (Shared) decThreadRef(dep, false); } - extern(C) void* rt_loadLibrary(const char* name) +public: + + export extern(C) void* rt_loadLibrary(const char* name) { immutable save = _rtLoading; _rtLoading = true; @@ -588,7 +590,7 @@ version (Shared) return handle; } - extern(C) int rt_unloadLibrary(void* handle) + export extern(C) int rt_unloadLibrary(void* handle) { if (handle is null) return false; @@ -601,6 +603,8 @@ version (Shared) decThreadRef(pdso, true); return .dlclose(handle) == 0; } + +private: } /////////////////////////////////////////////////////////////////////////////// diff --git a/druntime/src/rt/sections_osx_64.d b/druntime/src/rt/sections_osx_64.d index 5140826190ca..20457f1bee6e 100644 --- a/druntime/src/rt/sections_osx_64.d +++ b/druntime/src/rt/sections_osx_64.d @@ -31,6 +31,7 @@ version (X86_64_or_AArch64): // debug = PRINTF; import core.internal.container.array; +import core.atomic : atomicLoad; import core.stdc.stdint : intptr_t; import core.stdc.stdio : fprintf, stderr; import core.sys.darwin.mach.dyld : _dyld_register_func_for_add_image; @@ -137,12 +138,12 @@ extern (C) void sections_osx_onAddImage(const scope mach_header* h, intptr_t sli // take the sections from the last static image which is the executable if (_isRuntimeInitialized) { - fprintf(stderr, "Loading shared libraries isn't yet supported on OSX.\n"); + fprintf(atomicLoad(stderr), "Loading shared libraries isn't yet supported on OSX.\n"); return; } else if (_sections.modules.ptr !is null) { - fprintf(stderr, "Shared libraries are not yet supported on OSX.\n"); + fprintf(atomicLoad(stderr), "Shared libraries are not yet supported on OSX.\n"); } debug(PRINTF) printf(" minfodata\n"); diff --git a/druntime/src/rt/sections_win64.d b/druntime/src/rt/sections_win64.d index ab14bc02bb41..9ef9638be562 100644 --- a/druntime/src/rt/sections_win64.d +++ b/druntime/src/rt/sections_win64.d @@ -19,6 +19,7 @@ version (DigitalMars) version (Win64) version = hasEHTables; // debug = PRINTF; import core.internal.container.array; +import core.atomic : atomicLoad, atomicStore; import core.memory; import core.stdc.stdlib : calloc, free, malloc; import core.sys.windows.threadaux; @@ -120,9 +121,10 @@ void initSections(void* handle) nothrow @nogc cast(ulong)dataSection.length); import rt.sections; - conservative = !scanDataSegPrecisely(); + auto conservativeScan = !scanDataSegPrecisely(); + atomicStore(conservative, conservativeScan); - if (conservative) + if (conservativeScan) { sectionGroup._gcRanges = (cast(void[]*).malloc((void[]).sizeof))[0..1]; sectionGroup._gcRanges[0] = dataSection; @@ -214,7 +216,7 @@ version (Shared) void* beg = tlsarray[tls_index]; auto size = tlsdir.EndAddressOfRawData - tlsdir.StartAddressOfRawData + tlsdir.SizeOfZeroFill; - if (conservative) + if (atomicLoad(conservative)) dg(beg, beg + size); else scanTLSPrecise(cast(uint*)&sec._tpSection[0], cast(uint*)&sec._tpSection[$], beg, dg); @@ -276,7 +278,7 @@ void finiTLSRanges(void[] rng) nothrow @nogc void scanTLSRanges(void[] rng, scope void delegate(void* pbeg, void* pend) nothrow dg) nothrow { - if (conservative) + if (atomicLoad(conservative)) dg(rng.ptr, rng.ptr + rng.length); else scanTLSPrecise(&_TP_beg, &_TP_end, rng.ptr, dg); diff --git a/druntime/test/betterc/src/test19933.d b/druntime/test/betterc/src/test19933.d index d5c9eacf0df0..e13e16634dd8 100644 --- a/druntime/test/betterc/src/test19933.d +++ b/druntime/test/betterc/src/test19933.d @@ -2,10 +2,10 @@ // https://issues.dlang.org/show_bug.cgi?id=19933 // https://issues.dlang.org/show_bug.cgi?id=18816 -import core.stdc.stdio : fprintf, stderr; +import core.stdc.stdio : printf; extern(C) int main() { - fprintf(stderr, "Hello\n"); + printf("Hello\n"); return 0; } diff --git a/druntime/test/exceptions/src/catch_in_finally.d b/druntime/test/exceptions/src/catch_in_finally.d index 88aeafc0a1d8..008604d1397b 100644 --- a/druntime/test/exceptions/src/catch_in_finally.d +++ b/druntime/test/exceptions/src/catch_in_finally.d @@ -1,4 +1,10 @@ -import core.stdc.stdio : fprintf, stderr; +version (Posix) + import core.sys.posix.unistd : write; +else +{ + import core.atomic : atomicLoad; + import core.stdc.stdio : fprintf, stderr; +} class MyException : Exception { @@ -185,5 +191,13 @@ void main() { test3(); test4(); test5(); - fprintf(stderr, "success.\n"); + version (Posix) + { + // Avoid libc's shared stderr FILE* here: Alpine/musl crashes when this + // low-level exception test reaches it through atomicLoad(stderr). + enum message = "success.\n"; + write(2, message.ptr, message.length); + } + else + fprintf(atomicLoad(stderr), "success.\n"); } diff --git a/druntime/test/exceptions/src/future_message.d b/druntime/test/exceptions/src/future_message.d index c42cde5026f6..9617ff91f570 100644 --- a/druntime/test/exceptions/src/future_message.d +++ b/druntime/test/exceptions/src/future_message.d @@ -1,4 +1,10 @@ -import core.stdc.stdio : fprintf, stderr; +version (Posix) + import core.sys.posix.unistd : write; +else +{ + import core.atomic : atomicLoad; + import core.stdc.stdio : fprintf, stderr; +} // Make sure basic stuff works with future Throwable.message class NoMessage : Throwable @@ -56,7 +62,8 @@ void test(Throwable t) } catch (Throwable e) { - fprintf(stderr, "%.*s ", cast(int)e.message.length, e.message.ptr); + writeStderr(e.message); + writeStderr(" "); } } @@ -66,5 +73,23 @@ void main() test(new WithMessage("exception")); test(new WithMessageNoOverride("exception")); test(new WithMessageNoOverrideAndDifferentSignature("exception")); - fprintf(stderr, "\n"); + writeStderr("\n"); +} + +void writeStderr(const(char)[] message) +{ + version (Posix) + { + // Avoid libc's shared stderr FILE* here: Alpine/musl crashes when this + // low-level exception test reaches it through atomicLoad(stderr). + while (message.length) + { + const written = write(2, message.ptr, message.length); + if (written <= 0) + return; + message = message[written .. $]; + } + } + else + fprintf(atomicLoad(stderr), "%.*s", cast(int) message.length, message.ptr); } diff --git a/druntime/test/exceptions/src/linux_memory_error_handler.d b/druntime/test/exceptions/src/linux_memory_error_handler.d index 64a78ed971be..0eb3f7805e8e 100644 --- a/druntime/test/exceptions/src/linux_memory_error_handler.d +++ b/druntime/test/exceptions/src/linux_memory_error_handler.d @@ -1,5 +1,11 @@ import etc.linux.memoryerror; -import core.stdc.stdio : fprintf, stderr; +version (Posix) + import core.sys.posix.unistd : write; +else +{ + import core.atomic : atomicLoad; + import core.stdc.stdio : fprintf, stderr; +} void main() { @@ -39,5 +45,13 @@ void main() assert(deregisterMemoryErrorHandler()); } - fprintf(stderr, "success.\n"); + version (Posix) + { + // Avoid libc's shared stderr FILE* here: Alpine/musl crashes when this + // low-level exception test reaches it through atomicLoad(stderr). + enum message = "success.\n"; + write(2, message.ptr, message.length); + } + else + fprintf(atomicLoad(stderr), "success.\n"); } diff --git a/druntime/test/exceptions/src/static_dtor.d b/druntime/test/exceptions/src/static_dtor.d index 1d5520110dc3..fa3c48de02ee 100644 --- a/druntime/test/exceptions/src/static_dtor.d +++ b/druntime/test/exceptions/src/static_dtor.d @@ -1,11 +1,12 @@ // https://issues.dlang.org/show_bug.cgi?id=16594 +import core.atomic : atomicLoad; import core.stdc.stdio : fprintf, stderr; shared static ~this() { __gshared int count; - if (count++) fprintf(stderr, "dtor_called_more_than_once"); + if (count++) fprintf(atomicLoad(stderr), "dtor_called_more_than_once"); else throw new Exception("static_dtor_exception"); } diff --git a/druntime/test/shared/src/finalize.d b/druntime/test/shared/src/finalize.d index 15ecc589ba2d..61787d32bf3c 100644 --- a/druntime/test/shared/src/finalize.d +++ b/druntime/test/shared/src/finalize.d @@ -1,3 +1,5 @@ +import core.atomic : atomicLoad; +import core.internal.traits : Unshared; import core.runtime; import core.stdc.string : strrchr; import core.thread; @@ -47,7 +49,15 @@ void main(string[] args) static shared size_t finalizeCounter; SetFinalizeCounter setFinalizeCounter; loadSym(h, setFinalizeCounter, "setFinalizeCounter"); - setFinalizeCounter(&finalizeCounter); + alias UnsharedCounter = Unshared!(typeof(finalizeCounter)); + auto finalizeCounterPtr = (() @trusted + { + // This test must publish the address of a shared counter to a + // dynamically loaded library; the library performs the actual access + // atomically, but address formation itself has no atomic API. + return cast(shared(UnsharedCounter)*) &(*cast(UnsharedCounter*) &finalizeCounter); + })(); + setFinalizeCounter(finalizeCounterPtr); runTest(); auto thr = new Thread(&runTest); @@ -59,7 +69,7 @@ void main(string[] args) assert(0); static if (!isDlcloseNoop) { - if (finalizeCounter != 4) + if (atomicLoad(finalizeCounter) != 4) assert(0); } if (nf1._finalizeCounter) diff --git a/druntime/test/shared/src/lib.d b/druntime/test/shared/src/lib.d index 9772d4231089..ad48a42e9715 100644 --- a/druntime/test/shared/src/lib.d +++ b/druntime/test/shared/src/lib.d @@ -55,7 +55,7 @@ void testGC() } // test Init -import core.atomic : atomicOp; +import core.atomic : atomicLoad, atomicOp, atomicStore; shared uint shared_static_ctor, shared_static_dtor, static_ctor, static_dtor; shared static this() { _assert(atomicOp!"+="(shared_static_ctor, 1) == 1); } shared static ~this() { _assert(atomicOp!"+="(shared_static_dtor, 1) == 1); } @@ -85,18 +85,18 @@ void runTestsImpl() testGC(); - _assert(shared_static_ctor == 1); - _assert(static_ctor == 1); + _assert(atomicLoad(shared_static_ctor) == 1); + _assert(atomicLoad(static_ctor) == 1); static void run() { - _assert(static_ctor == 2); - _assert(shared_static_ctor == 1); + _assert(atomicLoad(static_ctor) == 2); + _assert(atomicLoad(shared_static_ctor) == 1); testGC(); } auto thr = new Thread(&run); thr.start(); thr.join(); - _assert(static_dtor == 1); + _assert(atomicLoad(static_dtor) == 1); passed = false; foreach (m; ModuleInfo) @@ -123,8 +123,7 @@ class MyFinalizer { ~this() { - import core.atomic; - atomicOp!"+="(*_finalizeCounter, 1); + atomicOp!"+="(*atomicLoad(_finalizeCounter), 1); } } @@ -135,7 +134,7 @@ class MyFinalizerBig : MyFinalizer extern(C) void setFinalizeCounter(shared(size_t)* p) { - _finalizeCounter = p; + atomicStore(_finalizeCounter, p); } version (DigitalMars) version (Windows) diff --git a/druntime/test/shared/src/link.d b/druntime/test/shared/src/link.d index 92aa9360f316..99b946ebc0cd 100644 --- a/druntime/test/shared/src/link.d +++ b/druntime/test/shared/src/link.d @@ -27,36 +27,36 @@ void testGC() lib.free(); } -import core.atomic : atomicOp; -shared static this() { _assert(lib.shared_static_ctor == 1); } -shared static ~this() { _assert(lib.shared_static_dtor == 0); } +import core.atomic : atomicLoad, atomicOp; +shared static this() { _assert(atomicLoad(lib.shared_static_ctor) == 1); } +shared static ~this() { _assert(atomicLoad(lib.shared_static_dtor) == 0); } shared uint static_ctor, static_dtor; -static this() { _assert(lib.static_ctor == atomicOp!"+="(static_ctor, 1)); } -static ~this() { _assert(lib.static_dtor + 1 == atomicOp!"+="(static_dtor, 1)); } +static this() { _assert(atomicLoad(lib.static_ctor) == atomicOp!"+="(static_ctor, 1)); } +static ~this() { _assert(atomicLoad(lib.static_dtor) + 1 == atomicOp!"+="(static_dtor, 1)); } void testInit() { import core.thread; - _assert(shared_static_ctor == 1); - _assert(static_ctor == 1); + _assert(atomicLoad(shared_static_ctor) == 1); + _assert(atomicLoad(static_ctor) == 1); - _assert(lib.static_ctor == 1); - _assert(lib.static_dtor == 0); + _assert(atomicLoad(lib.static_ctor) == 1); + _assert(atomicLoad(lib.static_dtor) == 0); static void foo() { - _assert(lib.shared_static_ctor == 1); - _assert(lib.shared_static_dtor == 0); - _assert(lib.static_ctor == 2); - _assert(lib.static_dtor == 0); + _assert(atomicLoad(lib.shared_static_ctor) == 1); + _assert(atomicLoad(lib.shared_static_dtor) == 0); + _assert(atomicLoad(lib.static_ctor) == 2); + _assert(atomicLoad(lib.static_dtor) == 0); } auto thr = new Thread(&foo); thr.start(); _assert(thr.join() is null); - _assert(lib.shared_static_ctor == 1); - _assert(lib.shared_static_dtor == 0); - _assert(lib.static_ctor == 2); - _assert(lib.static_dtor == 1); + _assert(atomicLoad(lib.shared_static_ctor) == 1); + _assert(atomicLoad(lib.shared_static_dtor) == 0); + _assert(atomicLoad(lib.static_ctor) == 2); + _assert(atomicLoad(lib.static_dtor) == 1); } void main() diff --git a/druntime/test/shared/src/load.d b/druntime/test/shared/src/load.d index a36fcffefcc9..a35ea67e5731 100644 --- a/druntime/test/shared/src/load.d +++ b/druntime/test/shared/src/load.d @@ -1,3 +1,4 @@ +import core.atomic : atomicLoad; import core.runtime; import core.stdc.string : strrchr; import core.thread; @@ -87,22 +88,22 @@ void testGC() void testInit() { - assert(*libStaticCtor == 1); - assert(*libStaticDtor == 0); + assert(atomicLoad(*libStaticCtor) == 1); + assert(atomicLoad(*libStaticDtor) == 0); static void run() { - assert(*libSharedStaticCtor == 1); - assert(*libSharedStaticDtor == 0); - assert(*libStaticCtor == 2); - assert(*libStaticDtor == 0); + assert(atomicLoad(*libSharedStaticCtor) == 1); + assert(atomicLoad(*libSharedStaticDtor) == 0); + assert(atomicLoad(*libStaticCtor) == 2); + assert(atomicLoad(*libStaticDtor) == 0); } auto thr = new Thread(&run); thr.start(); thr.join(); - assert(*libSharedStaticCtor == 1); - assert(*libSharedStaticDtor == 0); - assert(*libStaticCtor == 2); - assert(*libStaticDtor == 1); + assert(atomicLoad(*libSharedStaticCtor) == 1); + assert(atomicLoad(*libSharedStaticDtor) == 0); + assert(atomicLoad(*libStaticCtor) == 2); + assert(atomicLoad(*libStaticDtor) == 1); } const(ModuleInfo)* findModuleInfo(string name) diff --git a/druntime/test/shared/src/load_13414.d b/druntime/test/shared/src/load_13414.d index cdd7d9c1ec77..1ee15f11536e 100644 --- a/druntime/test/shared/src/load_13414.d +++ b/druntime/test/shared/src/load_13414.d @@ -21,11 +21,11 @@ void runTest(string name) const unloaded = Runtime.unloadLibrary(h); assert(unloaded); - assert(tlsDtor == 1); + assert(atomicLoad(tlsDtor) == 1); static if (isDlcloseNoop) - assert(dtor == 0); + assert(atomicLoad(dtor) == 0); else - assert(dtor == 1); + assert(atomicLoad(dtor) == 1); } void main(string[] args) diff --git a/druntime/test/shared/src/utils.di b/druntime/test/shared/src/utils.di index 825827fbcae6..95c894707f35 100644 --- a/druntime/test/shared/src/utils.di +++ b/druntime/test/shared/src/utils.di @@ -29,11 +29,18 @@ void loadSym(T)(void* handle, ref T val, const char* mangle) version (Windows) { import core.sys.windows.winbase : GetProcAddress; - val = cast(T) GetProcAddress(handle, mangle); + auto sym = GetProcAddress(handle, mangle); } else { import core.sys.posix.dlfcn : dlsym; - val = cast(T) dlsym(handle, mangle); + auto sym = dlsym(handle, mangle); } + static if (is(T == shared U, U)) + { + import core.atomic : atomicStore; + atomicStore(val, cast(T) sym); + } + else + val = cast(T) sym; } diff --git a/druntime/test/unittest/customhandler.d b/druntime/test/unittest/customhandler.d index 2beeb78ad82d..64780008d7b1 100644 --- a/druntime/test/unittest/customhandler.d +++ b/druntime/test/unittest/customhandler.d @@ -16,6 +16,6 @@ shared static this() void main() { - import core.stdc.stdio : fprintf, stderr; - fprintf(stderr, "main\n"); + import core.stdc.stdio : printf; + printf("main\n"); }