diff --git a/stdlib/cmake/modules/AddSwiftStdlib.cmake b/stdlib/cmake/modules/AddSwiftStdlib.cmake index b4392ffcd4643..ef1117d96e503 100644 --- a/stdlib/cmake/modules/AddSwiftStdlib.cmake +++ b/stdlib/cmake/modules/AddSwiftStdlib.cmake @@ -1822,6 +1822,45 @@ endfunction() # Presence of a build flavor requires SWIFT_MODULE_DEPENDS_MACCATALYST to be # defined and have values. # +# SWIFT_SOURCES_DEPENDS_MACOS +# Sources that are built when this library is being built for macOS +# +# SWIFT_SOURCES_DEPENDS_IOS +# Sources that are built when this library is being built for iOS +# +# SWIFT_SOURCES_DEPENDS_TVOS +# Sources that are built when this library is being built for tvOS +# +# SWIFT_SOURCES_DEPENDS_WATCHOS +# Sources that are built when this library is being built for watchOS +# +# SWIFT_SOURCES_DEPENDS_VISIONOS +# Sources that are built when this library is being built for visionOS +# +# SWIFT_SOURCES_DEPENDS_FREESTANDING +# Sources that are built when this library is being built for freestanding +# +# SWIFT_SOURCES_DEPENDS_FREEBSD +# Sources that are built when this library is being built for FreeBSD +# +# SWIFT_SOURCES_DEPENDS_OPENBSD +# Sources that are built when this library is being built for OpenBSD +# +# SWIFT_SOURCES_DEPENDS_LINUX +# Sources that are built when this library is being built for Linux +# +# SWIFT_SOURCES_DEPENDS_CYGWIN +# Sources that are built when this library is being built for Cygwin +# +# SWIFT_SOURCES_DEPENDS_HAIKU +# Sources that are built when this library is being built for Haiku +# +# SWIFT_SOURCES_DEPENDS_WASI +# Sources that are built when this library is being built for WASI +# +# SWIFT_SOURCES_DEPENDS_WINDOWS +# Sources that are built when this library is being built for Windows +# # source1 ... # Sources to add into this library. function(add_swift_target_library name) @@ -1897,7 +1936,20 @@ function(add_swift_target_library name) TARGET_SDKS SWIFT_COMPILE_FLAGS_MACCATALYST SWIFT_MODULE_DEPENDS_MACCATALYST - SWIFT_MODULE_DEPENDS_MACCATALYST_UNZIPPERED) + SWIFT_MODULE_DEPENDS_MACCATALYST_UNZIPPERED + SWIFT_SOURCES_DEPENDS_MACOS + SWIFT_SOURCES_DEPENDS_IOS + SWIFT_SOURCES_DEPENDS_TVOS + SWIFT_SOURCES_DEPENDS_WATCHOS + SWIFT_SOURCES_DEPENDS_VISIONOS + SWIFT_SOURCES_DEPENDS_FREESTANDING + SWIFT_SOURCES_DEPENDS_FREEBSD + SWIFT_SOURCES_DEPENDS_OPENBSD + SWIFT_SOURCES_DEPENDS_LINUX + SWIFT_SOURCES_DEPENDS_CYGWIN + SWIFT_SOURCES_DEPENDS_HAIKU + SWIFT_SOURCES_DEPENDS_WASI + SWIFT_SOURCES_DEPENDS_WINDOWS) cmake_parse_arguments(SWIFTLIB "${SWIFTLIB_options}" @@ -2168,6 +2220,36 @@ function(add_swift_target_library name) list(APPEND swiftlib_link_flags_all "-Xlinker" "-ignore_auto_link") endif() + # Append SDK specific sources to the full list of sources + set(sources ${SWIFTLIB_SOURCES}) + if(sdk STREQUAL "OSX") + list(APPEND sources ${SWIFTLIB_SWIFT_SOURCES_DEPENDS_MACOS}) + elseif(sdk STREQUAL "IOS" OR sdk STREQUAL "IOS_SIMULATOR") + list(APPEND sources ${SWIFTLIB_SWIFT_SOURCES_DEPENDS_IOS}) + elseif(sdk STREQUAL "TVOS" OR sdk STREQUAL "TVOS_SIMULATOR") + list(APPEND sources ${SWIFTLIB_SWIFT_SOURCES_DEPENDS_TVOS}) + elseif(sdk STREQUAL "WATCHOS" OR sdk STREQUAL "WATCHOS_SIMULATOR") + list(APPEND sources ${SWIFTLIB_SWIFT_SOURCES_DEPENDS_WATCHOS}) + elseif(sdk STREQUAL "XROS" OR sdk STREQUAL "XROS_SIMULATOR") + list(APPEND sources ${SWIFTLIB_SWIFT_SOURCES_DEPENDS_VISIONOS}) + elseif(sdk STREQUAL "FREESTANDING") + list(APPEND sources ${SWIFTLIB_SWIFT_SOURCES_DEPENDS_FREESTANDING}) + elseif(sdk STREQUAL "FREEBSD") + list(APPEND sources ${SWIFTLIB_SWIFT_SOURCES_DEPENDS_FREEBSD}) + elseif(sdk STREQUAL "OPENBSD") + list(APPEND sources ${SWIFTLIB_SWIFT_SOURCES_DEPENDS_OPENBSD}) + elseif(sdk STREQUAL "LINUX" OR sdk STREQUAL "ANDROID") + list(APPEND sources ${SWIFTLIB_SWIFT_SOURCES_DEPENDS_LINUX}) + elseif(sdk STREQUAL "CYGWIN") + list(APPEND sources ${SWIFTLIB_SWIFT_SOURCES_DEPENDS_CYGWIN}) + elseif(sdk STREQUAL "HAIKU") + list(APPEND sources ${SWIFTLIB_SWIFT_SOURCES_DEPENDS_HAIKU}) + elseif(sdk STREQUAL "WASI") + list(APPEND sources ${SWIFTLIB_SWIFT_SOURCES_DEPENDS_WASI}) + elseif(sdk STREQUAL "WINDOWS") + list(APPEND sources ${SWIFTLIB_SWIFT_SOURCES_DEPENDS_WINDOWS}) + endif() + # We unconditionally removed "-z,defs" from CMAKE_SHARED_LINKER_FLAGS in # swift_common_standalone_build_config_llvm within # SwiftSharedCMakeConfig.cmake, where it was added by a call to @@ -2372,7 +2454,7 @@ function(add_swift_target_library name) ${SWIFTLIB_NO_LINK_NAME_keyword} ${SWIFTLIB_OBJECT_LIBRARY_keyword} ${SWIFTLIB_INSTALL_WITH_SHARED_keyword} - ${SWIFTLIB_SOURCES} + ${sources} MODULE_TARGETS ${module_variant_names} SDK ${sdk} ARCHITECTURE ${arch} diff --git a/stdlib/public/SwiftShims/swift/shims/CMakeLists.txt b/stdlib/public/SwiftShims/swift/shims/CMakeLists.txt index db995535938f2..65d815968faba 100644 --- a/stdlib/public/SwiftShims/swift/shims/CMakeLists.txt +++ b/stdlib/public/SwiftShims/swift/shims/CMakeLists.txt @@ -24,6 +24,7 @@ set(sources Visibility.h _SwiftConcurrency.h _SwiftDistributed.h + _SynchronizationShims.h module.modulemap ) diff --git a/stdlib/public/SwiftShims/swift/shims/_SynchronizationShims.h b/stdlib/public/SwiftShims/swift/shims/_SynchronizationShims.h new file mode 100644 index 0000000000000..50fca8e39eca9 --- /dev/null +++ b/stdlib/public/SwiftShims/swift/shims/_SynchronizationShims.h @@ -0,0 +1,69 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_STDLIB_SYNCHRONIZATION_SHIMS_H +#define SWIFT_STDLIB_SYNCHRONIZATION_SHIMS_H + +#include "SwiftStdbool.h" +#include "SwiftStdint.h" + +#if defined(__linux__) +#include +#include +#include +#include + +static inline __swift_uint32_t _swift_stdlib_gettid() { + static __thread tid = 0; + + if (tid == 0) { + tid = syscall(SYS_gettid); + } + + return tid; +} + +static inline __swift_uint32_t _swift_stdlib_futex_lock(__swift_uint32_t *lock) { + int ret = syscall(SYS_futex, lock, FUTEX_LOCK_PI_PRIVATE, + /* val */ 0, // this value is ignored by this futex op + /* timeout */ NULL); // block indefinitely + + if (ret == 0) { + return ret; + } + + return errno; +} + +static inline __swift_uint32_t _swift_stdlib_futex_trylock(__swift_uint32_t *lock) { + int ret = syscall(SYS_futex, lock, FUTEX_TRYLOCK_PI); + + if (ret == 0) { + return ret; + } + + return errno; +} + +static inline __swift_uint32_t _swift_stdlib_futex_unlock(__swift_uint32_t *lock) { + int ret = syscall(SYS_futex, lock, FUTEX_UNLOCK_PI_PRIVATE); + + if (ret == 0) { + return ret; + } + + return errno; +} + +#endif // defined(__linux__) + +#endif // SWIFT_STDLIB_SYNCHRONIZATION_SHIMS_H diff --git a/stdlib/public/SwiftShims/swift/shims/module.modulemap b/stdlib/public/SwiftShims/swift/shims/module.modulemap index d2c11e3215f78..f6131a53c2ac2 100644 --- a/stdlib/public/SwiftShims/swift/shims/module.modulemap +++ b/stdlib/public/SwiftShims/swift/shims/module.modulemap @@ -31,3 +31,8 @@ module SwiftOverlayShims { header "LibcOverlayShims.h" export * } + +module _SynchronizationShims { + header "_SynchronizationShims.h" + export * +} diff --git a/stdlib/public/Synchronization/Atomic.swift b/stdlib/public/Synchronization/Atomics/Atomic.swift similarity index 80% rename from stdlib/public/Synchronization/Atomic.swift rename to stdlib/public/Synchronization/Atomics/Atomic.swift index d5c49f2543728..3818a3e271a80 100644 --- a/stdlib/public/Synchronization/Atomic.swift +++ b/stdlib/public/Synchronization/Atomics/Atomic.swift @@ -22,14 +22,14 @@ public struct Atomic { @available(SwiftStdlib 6.0, *) @_alwaysEmitIntoClient @_transparent - var address: UnsafeMutablePointer { - UnsafeMutablePointer(rawAddress) + var _address: UnsafeMutablePointer { + UnsafeMutablePointer(_rawAddress) } @available(SwiftStdlib 6.0, *) @_alwaysEmitIntoClient @_transparent - var rawAddress: Builtin.RawPointer { + var _rawAddress: Builtin.RawPointer { Builtin.unprotectedAddressOfBorrow(self) } @@ -40,7 +40,7 @@ public struct Atomic { @_alwaysEmitIntoClient @_transparent public init(_ initialValue: consuming Value) { - address.initialize(to: Value.encodeAtomicRepresentation(initialValue)) + _address.initialize(to: Value.encodeAtomicRepresentation(initialValue)) } // Deinit's can't be marked @_transparent. Do these things need all of these @@ -49,10 +49,10 @@ public struct Atomic { @_alwaysEmitIntoClient @inlinable deinit { - let oldValue = Value.decodeAtomicRepresentation(address.pointee) + let oldValue = Value.decodeAtomicRepresentation(_address.pointee) _ = consume oldValue - address.deinitialize(count: 1) + _address.deinitialize(count: 1) } } diff --git a/stdlib/public/Synchronization/AtomicBool.swift b/stdlib/public/Synchronization/Atomics/AtomicBool.swift similarity index 95% rename from stdlib/public/Synchronization/AtomicBool.swift rename to stdlib/public/Synchronization/Atomics/AtomicBool.swift index b12e8fef4f3e4..34f30469d136b 100644 --- a/stdlib/public/Synchronization/AtomicBool.swift +++ b/stdlib/public/Synchronization/Atomics/AtomicBool.swift @@ -93,31 +93,31 @@ extension Atomic where Value == Bool { let original = switch ordering { case .relaxed: Builtin.atomicrmw_and_monotonic_Int8( - rawAddress, + _rawAddress, builtinOperand ) case .acquiring: Builtin.atomicrmw_and_acquire_Int8( - rawAddress, + _rawAddress, builtinOperand ) case .releasing: Builtin.atomicrmw_and_release_Int8( - rawAddress, + _rawAddress, builtinOperand ) case .acquiringAndReleasing: Builtin.atomicrmw_and_acqrel_Int8( - rawAddress, + _rawAddress, builtinOperand ) case .sequentiallyConsistent: Builtin.atomicrmw_and_seqcst_Int8( - rawAddress, + _rawAddress, builtinOperand ) @@ -151,31 +151,31 @@ extension Atomic where Value == Bool { let original = switch ordering { case .relaxed: Builtin.atomicrmw_or_monotonic_Int8( - rawAddress, + _rawAddress, builtinOperand ) case .acquiring: Builtin.atomicrmw_or_acquire_Int8( - rawAddress, + _rawAddress, builtinOperand ) case .releasing: Builtin.atomicrmw_or_release_Int8( - rawAddress, + _rawAddress, builtinOperand ) case .acquiringAndReleasing: Builtin.atomicrmw_or_acqrel_Int8( - rawAddress, + _rawAddress, builtinOperand ) case .sequentiallyConsistent: Builtin.atomicrmw_or_seqcst_Int8( - rawAddress, + _rawAddress, builtinOperand ) @@ -209,31 +209,31 @@ extension Atomic where Value == Bool { let original = switch ordering { case .relaxed: Builtin.atomicrmw_xor_monotonic_Int8( - rawAddress, + _rawAddress, builtinOperand ) case .acquiring: Builtin.atomicrmw_xor_acquire_Int8( - rawAddress, + _rawAddress, builtinOperand ) case .releasing: Builtin.atomicrmw_xor_release_Int8( - rawAddress, + _rawAddress, builtinOperand ) case .acquiringAndReleasing: Builtin.atomicrmw_xor_acqrel_Int8( - rawAddress, + _rawAddress, builtinOperand ) case .sequentiallyConsistent: Builtin.atomicrmw_xor_seqcst_Int8( - rawAddress, + _rawAddress, builtinOperand ) diff --git a/stdlib/public/Synchronization/AtomicFloats.swift b/stdlib/public/Synchronization/Atomics/AtomicFloats.swift similarity index 100% rename from stdlib/public/Synchronization/AtomicFloats.swift rename to stdlib/public/Synchronization/Atomics/AtomicFloats.swift diff --git a/stdlib/public/Synchronization/AtomicIntegers.swift.gyb b/stdlib/public/Synchronization/Atomics/AtomicIntegers.swift.gyb similarity index 99% rename from stdlib/public/Synchronization/AtomicIntegers.swift.gyb rename to stdlib/public/Synchronization/Atomics/AtomicIntegers.swift.gyb index b13d19dec1ccd..32ea1b7f0212d 100644 --- a/stdlib/public/Synchronization/AtomicIntegers.swift.gyb +++ b/stdlib/public/Synchronization/Atomics/AtomicIntegers.swift.gyb @@ -120,12 +120,12 @@ extension Atomic where Value == ${intType} { % if intType == "Int" or intType == "UInt": #if _pointerBitWidth(_64) Builtin.atomicrmw_${atomicOperationName(intType, builtinName)}_${llvmOrder}_Int64( - rawAddress, + _rawAddress, operand._value ) #elseif _pointerBitWidth(_32) Builtin.atomicrmw_${atomicOperationName(intType, builtinName)}_${llvmOrder}_Int32( - rawAddress, + _rawAddress, operand._value ) #else @@ -133,7 +133,7 @@ extension Atomic where Value == ${intType} { #endif % else: Builtin.atomicrmw_${atomicOperationName(intType, builtinName)}_${llvmOrder}_Int${bits}( - rawAddress, + _rawAddress, operand._value ) % end diff --git a/stdlib/public/Synchronization/AtomicLazyReference.swift b/stdlib/public/Synchronization/Atomics/AtomicLazyReference.swift similarity index 100% rename from stdlib/public/Synchronization/AtomicLazyReference.swift rename to stdlib/public/Synchronization/Atomics/AtomicLazyReference.swift diff --git a/stdlib/public/Synchronization/AtomicMemoryOrderings.swift b/stdlib/public/Synchronization/Atomics/AtomicMemoryOrderings.swift similarity index 100% rename from stdlib/public/Synchronization/AtomicMemoryOrderings.swift rename to stdlib/public/Synchronization/Atomics/AtomicMemoryOrderings.swift diff --git a/stdlib/public/Synchronization/AtomicOptional.swift b/stdlib/public/Synchronization/Atomics/AtomicOptional.swift similarity index 100% rename from stdlib/public/Synchronization/AtomicOptional.swift rename to stdlib/public/Synchronization/Atomics/AtomicOptional.swift diff --git a/stdlib/public/Synchronization/AtomicPointers.swift b/stdlib/public/Synchronization/Atomics/AtomicPointers.swift similarity index 100% rename from stdlib/public/Synchronization/AtomicPointers.swift rename to stdlib/public/Synchronization/Atomics/AtomicPointers.swift diff --git a/stdlib/public/Synchronization/AtomicRepresentable.swift b/stdlib/public/Synchronization/Atomics/AtomicRepresentable.swift similarity index 100% rename from stdlib/public/Synchronization/AtomicRepresentable.swift rename to stdlib/public/Synchronization/Atomics/AtomicRepresentable.swift diff --git a/stdlib/public/Synchronization/AtomicStorage.swift.gyb b/stdlib/public/Synchronization/Atomics/AtomicStorage.swift.gyb similarity index 98% rename from stdlib/public/Synchronization/AtomicStorage.swift.gyb rename to stdlib/public/Synchronization/Atomics/AtomicStorage.swift.gyb index 91e5ff065095e..a0779b7e54f18 100644 --- a/stdlib/public/Synchronization/AtomicStorage.swift.gyb +++ b/stdlib/public/Synchronization/Atomics/AtomicStorage.swift.gyb @@ -53,7 +53,7 @@ extension Atomic where Value.AtomicRepresentation == ${type} { let result = switch ordering { % for (name, api, doc, llvm) in loadOrderings: case .${name}: - Builtin.atomicload_${llvm}_Int${size}(rawAddress) + Builtin.atomicload_${llvm}_Int${size}(_rawAddress) % end default: @@ -87,7 +87,7 @@ extension Atomic where Value.AtomicRepresentation == ${type} { % for (name, api, doc, llvm) in storeOrderings: case .${name}: Builtin.atomicstore_${llvm}_Int${size}( - rawAddress, + _rawAddress, Value.encodeAtomicRepresentation(desired)._storage ) % end @@ -123,7 +123,7 @@ extension Atomic where Value.AtomicRepresentation == ${type} { let result = switch ordering { % for (name, api, _, llvm, _) in updateOrderings: case .${name}: - Builtin.atomicrmw_xchg_${llvm}_Int${size}(rawAddress, desired) + Builtin.atomicrmw_xchg_${llvm}_Int${size}(_rawAddress, desired) % end default: @@ -229,7 +229,7 @@ extension Atomic where Value.AtomicRepresentation == ${type} { % for (failureName, _, _, failureLLVM) in loadOrderings: case (.${successName}, .${failureName}): Builtin.cmpxchg_${actualOrders(successLLVM, failureLLVM)}_Int${size}( - rawAddress, + _rawAddress, expected, desired ) @@ -359,7 +359,7 @@ extension Atomic where Value.AtomicRepresentation == ${type} { % for (failureName, _, _, failureLLVM) in loadOrderings: case (.${successName}, .${failureName}): Builtin.cmpxchg_${actualOrders(successLLVM, failureLLVM)}_weak_Int${size}( - rawAddress, + _rawAddress, expected, desired ) diff --git a/stdlib/public/Synchronization/WordPair.swift b/stdlib/public/Synchronization/Atomics/WordPair.swift similarity index 100% rename from stdlib/public/Synchronization/WordPair.swift rename to stdlib/public/Synchronization/Atomics/WordPair.swift diff --git a/stdlib/public/Synchronization/CMakeLists.txt b/stdlib/public/Synchronization/CMakeLists.txt index 7a1de4db90eea..e363916f4737f 100644 --- a/stdlib/public/Synchronization/CMakeLists.txt +++ b/stdlib/public/Synchronization/CMakeLists.txt @@ -10,32 +10,116 @@ # #===----------------------------------------------------------------------===# -set(swift_synchronization_sources - Atomic.swift - AtomicBool.swift - AtomicFloats.swift - AtomicLazyReference.swift - AtomicMemoryOrderings.swift - AtomicOptional.swift - AtomicPointers.swift - AtomicRepresentable.swift - WordPair.swift +set(SWIFT_SYNCHRONIZATION_ATOMIC_SOURCES + Atomics/Atomic.swift + Atomics/AtomicBool.swift + Atomics/AtomicFloats.swift + Atomics/AtomicLazyReference.swift + Atomics/AtomicMemoryOrderings.swift + Atomics/AtomicOptional.swift + Atomics/AtomicPointers.swift + Atomics/AtomicRepresentable.swift + Atomics/WordPair.swift ) -set(swift_synchronization_gyb_sources - AtomicIntegers.swift.gyb - AtomicStorage.swift.gyb + +set(SWIFT_SYNCHRONIZATION_SOURCES + ${SWIFT_SYNCHRONIZATION_ATOMIC_SOURCES} + + Cell.swift +) + +set(SWIFT_SYNCHRONIZATION_GYB_SOURCES + Atomics/AtomicIntegers.swift.gyb + Atomics/AtomicStorage.swift.gyb +) + +# Darwin dependencies and sources + +set(SWIFT_SYNCHRONIZATION_DARWIN_DEPENDENCIES) + +if(SWIFT_BUILD_SDK_OVERLAY) + set(SWIFT_SYNCHRONIZATION_DARWIN_DEPENDENCIES Darwin) +endif() + +set(SWIFT_SYNCHRONIZATION_DARWIN_SOURCES + Mutex/DarwinImpl.swift + Mutex/Mutex.swift +) + +# Linux sources + +set(SWIFT_SYNCHRONIZATION_LINUX_SOURCES + Mutex/LinuxImpl.swift + Mutex/Mutex.swift + Mutex/SpinLoopHint.swift +) + +# Wasm sources + +set(SWIFT_SYNCHRONIZATION_WASM_SOURCES + Mutex/Mutex.swift + Mutex/WasmImpl.swift +) + +# Windows sources + +set(SWIFT_SYNCHRONIZATION_WINDOWS_SOURCES + Mutex/Mutex.swift + Mutex/WindowsImpl.swift ) -set(swift_synchronization_swift_compile_flags + +set(SWIFT_SYNCHRNOIZATION_SWIFT_FLAGS ${SWIFT_STANDARD_LIBRARY_SWIFT_FLAGS} "-enable-builtin-module" "-enable-experimental-feature" "RawLayout" "-enable-experimental-feature" "StaticExclusiveOnly" + "-enable-experimental-feature" "Extern" ) add_swift_target_library(swiftSynchronization ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB - ${swift_synchronization_sources} - GYB_SOURCES ${swift_synchronization_gyb_sources} - SWIFT_COMPILE_FLAGS ${swift_synchronization_swift_compile_flags} + ${SWIFT_SYNCHRONIZATION_SOURCES} + + GYB_SOURCES + ${SWIFT_SYNCHRONIZATION_GYB_SOURCES} + + SWIFT_SOURCES_DEPENDS_MACOS + ${SWIFT_SYNCHRONIZATION_DARWIN_SOURCES} + SWIFT_SOURCES_DEPENDS_IOS + ${SWIFT_SYNCHRONIZATION_DARWIN_SOURCES} + SWIFT_SOURCES_DEPENDS_TVOS + ${SWIFT_SYNCHRONIZATION_DARWIN_SOURCES} + SWIFT_SOURCES_DEPENDS_WATCHOS + ${SWIFT_SYNCHRONIZATION_DARWIN_SOURCES} + SWIFT_SOURCES_DEPENDS_VISIONOS + ${SWIFT_SYNCHRONIZATION_DARWIN_SOURCES} + SWIFT_SOURCES_DEPENDS_LINUX + ${SWIFT_SYNCHRONIZATION_LINUX_SOURCES} + SWIFT_SOURCES_DEPENDS_WASI + ${SWIFT_SYNCHRONIZATION_WASM_SOURCES} + SWIFT_SOURCES_DEPENDS_WINDOWS + ${SWIFT_SYNCHRONIZATION_WINDOWS_SOURCES} + SWIFT_SOURCES_DEPENDS_FREESTANDING + Mutex/MutexUnavailable.swift + + SWIFT_MODULE_DEPENDS_OSX + ${SWIFT_SYNCHRONIZATION_DARWIN_DEPENDENCIES} + SWIFT_MODULE_DEPENDS_IOS + ${SWIFT_SYNCHRONIZATION_DARWIN_DEPENDENCIES} + SWIFT_MODULE_DEPENDS_TVOS + ${SWIFT_SYNCHRONIZATION_DARWIN_DEPENDENCIES} + SWIFT_MODULE_DEPENDS_WATCHOS + ${SWIFT_SYNCHRONIZATION_DARWIN_DEPENDENCIES} + SWIFT_MODULE_DEPENDS_XROS + ${SWIFT_SYNCHRONIZATION_DARWIN_DEPENDENCIES} + SWIFT_MODULE_DEPENDS_MACCATALYST + ${SWIFT_SYNCHRONIZATION_DARWIN_DEPENDENCIES} + SWIFT_MODULE_DEPENDS_LINUX + Glibc + SWIFT_MODULE_DEPENDS_WINDOWS + WinSDK + + SWIFT_COMPILE_FLAGS + ${SWIFT_SYNCHRNOIZATION_SWIFT_FLAGS} LINK_FLAGS "${SWIFT_RUNTIME_SWIFT_LINK_FLAGS}" INSTALL_IN_COMPONENT @@ -68,11 +152,12 @@ if(SWIFT_SHOULD_BUILD_EMBEDDED_STDLIB) ONLY_SWIFTMODULE IS_FRAGILE - ${swift_synchronization_sources} - GYB_SOURCES ${swift_synchronization_gyb_sources} + ${SWIFT_SYNCHRONIZATION_ATOMIC_SOURCES} + GYB_SOURCES + ${SWIFT_SYNCHRONIZATION_GYB_SOURCES} SWIFT_COMPILE_FLAGS - ${swift_synchronization_swift_compile_flags} + ${SWIFT_SYNCHRNOIZATION_SWIFT_FLAGS} -Xcc -D__MACH__ -Xcc -D__APPLE__ -Xcc -ffreestanding -enable-experimental-feature Embedded MODULE_DIR "${CMAKE_BINARY_DIR}/lib/swift/embedded" diff --git a/stdlib/public/Synchronization/Cell.swift b/stdlib/public/Synchronization/Cell.swift new file mode 100644 index 0000000000000..c893952004782 --- /dev/null +++ b/stdlib/public/Synchronization/Cell.swift @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Atomics open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Builtin + +@available(SwiftStdlib 6.0, *) +@frozen +@usableFromInline +@_rawLayout(like: Value, movesAsLike) +internal struct _Cell: ~Copyable { + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + internal var _address: UnsafeMutablePointer { + UnsafeMutablePointer(_rawAddress) + } + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + internal var _rawAddress: Builtin.RawPointer { +#if $BuiltinAddressOfRawLayout + Builtin.addressOfRawLayout(self) +#else + fatalError() +#endif + } + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + internal init(_ initialValue: consuming Value) { + _address.initialize(to: initialValue) + } + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @inlinable + deinit { + _address.deinitialize(count: 1) + } +} diff --git a/stdlib/public/Synchronization/Mutex/DarwinImpl.swift b/stdlib/public/Synchronization/Mutex/DarwinImpl.swift new file mode 100644 index 0000000000000..7b0afef515542 --- /dev/null +++ b/stdlib/public/Synchronization/Mutex/DarwinImpl.swift @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Atomics open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Darwin + +@available(SwiftStdlib 6.0, *) +@frozen +@_staticExclusiveOnly +public struct _MutexHandle: ~Copyable { + @usableFromInline + let value: _Cell + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + public init() { + value = _Cell(os_unfair_lock()) + } + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + internal borrowing func _lock() { + os_unfair_lock_lock(value._address) + } + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + internal borrowing func _tryLock() -> Bool { + os_unfair_lock_trylock(value._address) + } + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + internal borrowing func _unlock() { + os_unfair_lock_unlock(value._address) + } +} diff --git a/stdlib/public/Synchronization/Mutex/LinuxImpl.swift b/stdlib/public/Synchronization/Mutex/LinuxImpl.swift new file mode 100644 index 0000000000000..ffb05b4f45b31 --- /dev/null +++ b/stdlib/public/Synchronization/Mutex/LinuxImpl.swift @@ -0,0 +1,362 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Atomics open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import _SynchronizationShims +import Glibc + +extension Atomic where Value == UInt32 { + // This returns 'false' on success and 'true' on error. Check 'errno' for the + // specifc error value. + internal borrowing func _futexLock() -> UInt32 { + _swift_stdlib_futex_lock(.init(_rawAddress)) + } + + // This returns 'false' on success and 'true' on error. Check 'errno' for the + // specifc error value. + internal borrowing func _futexTryLock() -> UInt32 { + _swift_stdlib_futex_trylock(.init(_rawAddress)) + } + + // This returns 'false' on success and 'true' on error. Check 'errno' for the + // specific error value. + internal borrowing func _futexUnlock() -> UInt32 { + _swift_stdlib_futex_unlock(.init(_rawAddress)) + } +} + +@available(SwiftStdlib 6.0, *) +@frozen +@_staticExclusiveOnly +public struct _MutexHandle: ~Copyable { + // There are only 3 different values that storage can hold at a single time. + // 0: unlocked + // TID: locked, current thread's id (uncontended) + // (TID | FUTEX_WAITERS): locked, current thread's id (contended) + @usableFromInline + let storage: Atomic + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + public init() { + storage = Atomic(0) + } +} + +@available(SwiftStdlib 6.0, *) +extension _MutexHandle { + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + internal borrowing func _lock() { + // Note: This is being TLS cached. + let selfId = _swift_stdlib_gettid() + + let (exchanged, _) = storage.compareExchange( + expected: 0, + desired: selfId, + successOrdering: .acquiring, + failureOrdering: .relaxed + ) + + if _fastPath(exchanged) { + // Locked! + return + } + + _lockSlow(selfId) + } + + @available(SwiftStdlib 6.0, *) + @usableFromInline + internal borrowing func _lockSlow(_ selfId: UInt32) { + // Before relinquishing control to the kernel to block this particular + // thread, run a little spin lock to keep this thread busy in the scenario + // where the current owner thread's critical section is somewhat quick. We + // avoid a lot of the syscall overhead in these cases which allow both the + // owner thread and this current thread to do the user-space atomic for + // releasing and acquiring (assuming no existing waiters). The waiter bit is + // typically unset when a call to 'FUTEX_UNLOCK_PI' has no other pi state, + // meaning there is no one else waiting to acquire the lock. + do { + // This value is controlled on a per architecture bases defined in + // 'SpinLoopHint.swift'. + var tries = _tries + + repeat { + // Do a relaxed load of the futex value to prevent introducing a memory + // barrier on each iteration of this loop. We're already informing the + // CPU that this is a spin loop via the '_spinLoopHint' call which + // should hopefully slow down the loop a considerable amount to view an + // actually change in the value potentially. An extra memory barrier + // would make it even slower on top of the fact that we may not even be + // able to attempt to acquire the lock. + let state = storage.load(ordering: .relaxed) + + if state == 0, storage.compareExchange( + expected: 0, + desired: selfId, + successOrdering: .acquiring, + failureOrdering: .relaxed + ).exchanged { + // Locked! + return + } + + tries &-= 1 + + // Inform the CPU that we're doing a spin loop which should have the + // effect of slowing down this loop if only by a little to preserve + // energy. + _spinLoopHint() + } while tries != 0 + } + + // We've exhausted our spins. Ask the kernel to block for us until the owner + // releases the lock. + // + // Note: The kernel will attempt to acquire the lock for us as well which + // could succeed if the owner releases in between finishing spinning the + // futex syscall. + while true { + // Block until an equivalent '_futexUnlock' has been called by the owner. + // This returns '0' on success which means the kernel has acquired the + // lock for us. + switch storage._futexLock() { + case 0: + // Locked! + return + + // EINTR - "A FUTEX_WAIT or FUTEX_WAIT_BITSET operation was interrupted + // by a signal (see signal(7)). Before Linux 2.6.22, this error + // could also be returned for a spurious wakeup; since Linux + // 2.6.22, this no longer happens." + // EAGAIN - "The futex owner thread ID of uaddr is about to exit, but has + // not yet handled the internal state cleanup. Try again." + case 4, 11: + continue + + // EDEADLK - "The futex word at uaddr is already locked by the caller." + case 35: + // TODO: Replace with a colder function / one that takes a StaticString + fatalError("Recursive call to lock Mutex") + + // This handles all of the following errors which generally aren't + // applicable to this implementation: + // + // EACCES - "No read access to the memory of a futex word." + // EFAULT - "A required pointer argument did not point to a valid + // user-space address." + // EINVAL - "The operation in futex_op is one of those that employs a + // timeout, but the supplied timeout argument was invalid + // (tv_sec was less than zero, or tv_nsec was not less than + // 1,000,000,000)." + // OR + // "The operation specified in futex_op employs one or both of + // the pointers uaddr and uaddr2, but one of these does not + // point to a valid object—that is, the address is not four- + // byte-aligned." + // OR + // "The kernel detected an inconsistency between the user-space + // state at uaddr and the kernel state. This indicates either + // state corruption or that the kernel found a waiter on uaddr + // which is waiting via FUTEX_WAIT or FUTEX_WAIT_BITSET." + // OR + // "Invalid argument." + // ENOMEM - "The kernel could not allocate memory to hold state + // information." + // ENOSYS - "Invalid operation specified in futex_op." + // OR + // "A run-time check determined that the operation is not + // available. The PI-futex operations are not implemented on all + // architectures and are not supported on some CPU variants." + // EPERM - "The caller is not allowed to attach itself to the futex at + // uaddr (This may be caused by a state corruption in user + // space.)" + // ESRCH - "The thread ID in the futex word at uaddr does not exist." + default: + // TODO: Replace with a colder function / one that takes a StaticString + fatalError("Unknown error occured while attempting to acquire a Mutex") + } + } + } + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + internal borrowing func _tryLock() -> Bool { + // Do a user space cmpxchg to see if we can easily acquire the lock. + if storage.compareExchange( + expected: 0, + + // Note: This is being TLS cached. + desired: _swift_stdlib_gettid(), + successOrdering: .acquiring, + failureOrdering: .relaxed + ).exchanged { + // Locked! + return true + } + + // The quick atomic op failed, ask the kernel to see if it can acquire the + // lock for us. + return _tryLockSlow() + } + + @available(SwiftStdlib 6.0, *) + @usableFromInline + internal borrowing func _tryLockSlow() -> Bool { + // Note: "Because the kernel has access to more state information than user + // space, acquisition of the lock might succeed if performed by the + // kernel in cases where the futex word (i.e., the state information + // accessible to use-space) contains stale state (FUTEX_WAITERS + // and/or FUTEX_OWNER_DIED). This can happen when the owner of the + // futex died. User space cannot handle this condition in a race-free + // manner, but the kernel can fix this up and acquire the futex." + switch storage._futexTryLock() { + case 0: + // Locked! + return true + + // EDEADLK - "The futex word at uaddr is already locked by the caller." + case 35: + // TODO: Replace with a colder function / one that takes a StaticString + fatalError("Attempt to try to lock Mutex in already acquired thread") + + // This handles all of the following errors which generally aren't + // applicable to this implementation: + // + // EACCES - "No read access to the memory of a futex word." + // EAGAIN - "The futex owner thread ID of uaddr is about to exit, but has + // not yet handled the internal state cleanup. Try again." + // EFAULT - "A required pointer argument did not point to a valid + // user-space address." + // EINVAL - "The operation in futex_op is one of those that employs a + // timeout, but the supplied timeout argument was invalid + // (tv_sec was less than zero, or tv_nsec was not less than + // 1,000,000,000)." + // OR + // "The operation specified in futex_op employs one or both of + // the pointers uaddr and uaddr2, but one of these does not + // point to a valid object—that is, the address is not four- + // byte-aligned." + // OR + // "The kernel detected an inconsistency between the user-space + // state at uaddr and the kernel state. This indicates either + // state corruption or that the kernel found a waiter on uaddr + // which is waiting via FUTEX_WAIT or FUTEX_WAIT_BITSET." + // OR + // "Invalid argument." + // ENOMEM - "The kernel could not allocate memory to hold state + // information." + // ENOSYS - "Invalid operation specified in futex_op." + // OR + // "A run-time check determined that the operation is not + // available. The PI-futex operations are not implemented on all + // architectures and are not supported on some CPU variants." + // EPERM - "The caller is not allowed to attach itself to the futex at + // uaddr (This may be caused by a state corruption in user + // space.)" + // ESRCH - "The thread ID in the futex word at uaddr does not exist." + default: + // Note: We could maybe retry this operation when given EAGAIN, but this + // is more or less supposed to be a quick yes/no. + return false + } + } + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + internal borrowing func _unlock() { + // Note: This is being TLS cached. + let selfId = _swift_stdlib_gettid() + + // Attempt to release the lock. We can only atomically release the lock in + // user-space when there are no other waiters. If there are waiters, the + // waiter bit is set and we need to inform the kernel that we're unlocking. + let (exchanged, _) = storage.compareExchange( + expected: selfId, + desired: 0, + successOrdering: .releasing, + failureOrdering: .relaxed + ) + + if _fastPath(exchanged) { + // No waiters, unlocked! + return + } + + _unlockSlow() + } + + @available(SwiftStdlib 6.0, *) + @usableFromInline + internal borrowing func _unlockSlow() { + while true { + switch storage._futexUnlock() { + case 0: + // Unlocked! + return + + // EINTR - "A FUTEX_WAIT or FUTEX_WAIT_BITSET operation was interrupted + // by a signal (see signal(7)). Before Linux 2.6.22, this error + // could also be returned for a spurious wakeup; since Linux + // 2.6.22, this no longer happens." + case 4: + continue + + // EPERM - "The caller does not own the lock represented by the futex + // word." + case 1: + // TODO: Replace with a colder function / one that takes a StaticString + fatalError( + "Call to unlock Mutex on a thread which hasn't acquired the lock" + ) + + // This handles all of the following errors which generally aren't + // applicable to this implementation: + // + // EACCES - "No read access to the memory of a futex word." + // EFAULT - "A required pointer argument did not point to a valid + // user-space address." + // EINVAL - "The operation in futex_op is one of those that employs a + // timeout, but the supplied timeout argument was invalid + // (tv_sec was less than zero, or tv_nsec was not less than + // 1,000,000,000)." + // OR + // "The operation specified in futex_op employs one or both of + // the pointers uaddr and uaddr2, but one of these does not + // point to a valid object—that is, the address is not four- + // byte-aligned." + // OR + // "The kernel detected an inconsistency between the user-space + // state at uaddr and the kernel state. This indicates either + // state corruption or that the kernel found a waiter on uaddr + // which is waiting via FUTEX_WAIT or FUTEX_WAIT_BITSET." + // OR + // "Invalid argument." + // ENOSYS - "Invalid operation specified in futex_op." + // OR + // "A run-time check determined that the operation is not + // available. The PI-futex operations are not implemented on all + // architectures and are not supported on some CPU variants." + // EPERM - "The caller is not allowed to attach itself to the futex at + // uaddr (This may be caused by a state corruption in user + // space.)" + default: + // TODO: Replace with a colder function / one that takes a StaticString + fatalError("Unknown error occured while attempting to release a Mutex") + } + } + } +} diff --git a/stdlib/public/Synchronization/Mutex/Mutex.swift b/stdlib/public/Synchronization/Mutex/Mutex.swift new file mode 100644 index 0000000000000..ea91b4d452513 --- /dev/null +++ b/stdlib/public/Synchronization/Mutex/Mutex.swift @@ -0,0 +1,195 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Atomics open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// A synchronization primitive that protects shared mutable state via +/// mutual exclusion. +/// +/// The `Mutex` type offers non-recursive exclusive access to the state +/// it is protecting by blocking threads attempting to acquire the lock. +/// Only one execution context at a time has access to the value stored +/// within the `Mutex` allowing for exclusive access. +/// +/// An example use of `Mutex` in a class used simultaneously by many +/// threads protecting a `Dictionary` value: +/// +/// class Manager { +/// let cache = Mutex<[Key: Resource]>([:]) +/// +/// func saveResouce(_ resource: Resouce, as key: Key) { +/// cache.withLock { +/// $0[key] = resource +/// } +/// } +/// } +/// +@available(SwiftStdlib 6.0, *) +@frozen +@_staticExclusiveOnly +public struct Mutex: ~Copyable { + @usableFromInline + let handle = _MutexHandle() + + @usableFromInline + let value: _Cell + + /// Initializes a value of this mutex with the given initial state. + /// + /// - Parameter initialValue: The initial value to give to the mutex. + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + public init(_ initialValue: consuming sending Value) { + value = _Cell(initialValue) + } +} + +@available(SwiftStdlib 6.0, *) +extension Mutex: @unchecked Sendable where Value: ~Copyable {} + +@available(SwiftStdlib 6.0, *) +extension Mutex where Value: ~Copyable { + /// Calls the given closure after acquring the lock and then releases + /// ownership. + /// + /// This method is equivalent to the following sequence of code: + /// + /// mutex.lock() + /// defer { + /// mutex.unlock() + /// } + /// return try body(&value) + /// + /// - Warning: Recursive calls to `withLock` within the + /// closure parameter has behavior that is platform dependent. + /// Some platforms may choose to panic the process, deadlock, + /// or leave this behavior unspecified. This will never + /// reacquire the lock however. + /// + /// - Parameter body: A closure with a parameter of `Value` + /// that has exclusive access to the value being stored within + /// this mutex. This closure is considered the critical section + /// as it will only be executed once the calling thread has + /// acquired the lock. + /// + /// - Returns: The return value, if any, of the `body` closure parameter. + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + public borrowing func withLock( + _ body: @Sendable (inout Value) throws(E) -> sending Result + ) throws(E) -> sending Result { + handle._lock() + + defer { + handle._unlock() + } + + return try body(&value._address.pointee) + } + + /// Attempts to acquire the lock and then calls the given closure if + /// successful. + /// + /// If the calling thread was successful in acquiring the lock, the + /// closure will be executed and then immediately after it will + /// release ownership of the lock. If we were unable to acquire the + /// lock, this will return `nil`. + /// + /// This method is equivalent to the following sequence of code: + /// + /// guard mutex.tryLock() else { + /// return nil + /// } + /// defer { + /// mutex.unlock() + /// } + /// return try body(&value) + /// + /// - Warning: Recursive calls to `withLockIfAvailable` within the + /// closure parameter has behavior that is platform dependent. + /// Some platforms may choose to panic the process, deadlock, + /// or leave this behavior unspecified. This will never + /// reacquire the lock however. + /// + /// - Parameter body: A closure with a parameter of `Value` + /// that has exclusive access to the value being stored within + /// this mutex. This closure is considered the critical section + /// as it will only be executed if the calling thread acquires + /// the lock. + /// + /// - Returns: The return value, if any, of the `body` closure parameter + /// or nil if the lock couldn't be acquired. + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + public borrowing func withLockIfAvailable( + _ body: @Sendable (inout Value) throws(E) -> sending Result + ) throws(E) -> sending Result? { + guard handle._tryLock() else { + return nil + } + + defer { + handle._unlock() + } + + return try body(&value._address.pointee) + } +} + +@available(SwiftStdlib 6.0, *) +extension Mutex where Value == Void { + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + public borrowing func _unsafeLock() { + handle._lock() + } + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + public borrowing func _unsafeTryLock() -> Bool { + handle._tryLock() + } + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + public borrowing func _unsafeUnlock() { + handle._unlock() + } +} + +@available(SwiftStdlib 6.0, *) +extension _MutexHandle { + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + public borrowing func unsafeLock() { + _lock() + } + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + public borrowing func unsafeTryLock() -> Bool { + _tryLock() + } + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + public borrowing func unsafeUnlock() { + _unlock() + } +} diff --git a/stdlib/public/Synchronization/Mutex/MutexUnavailable.swift b/stdlib/public/Synchronization/Mutex/MutexUnavailable.swift new file mode 100644 index 0000000000000..e599d01966488 --- /dev/null +++ b/stdlib/public/Synchronization/Mutex/MutexUnavailable.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Atomics open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// A synchronization primitive that protects shared mutable state via +/// mutual exclusion. +/// +/// The `Mutex` type offers non-recursive exclusive access to the state +/// it is protecting by blocking threads attempting to acquire the lock. +/// Only one execution context at a time has access to the value stored +/// within the `Mutex` allowing for exclusive access. +/// +/// An example use of `Mutex` in a class used simultaneously by many +/// threads protecting a `Dictionary` value: +/// +/// class Manager { +/// let cache = Mutex<[Key: Resource]>([:]) +/// +/// func saveResouce(_ resource: Resouce, as key: Key) { +/// cache.withLock { +/// $0[key] = resource +/// } +/// } +/// } +/// +@available(*, unavailable, message: "Mutex is not available on this platform") +@frozen +@_staticExclusiveOnly +public struct Mutex: ~Copyable {} diff --git a/stdlib/public/Synchronization/Mutex/SpinLoopHint.swift b/stdlib/public/Synchronization/Mutex/SpinLoopHint.swift new file mode 100644 index 0000000000000..0084e1d056892 --- /dev/null +++ b/stdlib/public/Synchronization/Mutex/SpinLoopHint.swift @@ -0,0 +1,87 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Atomics open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#if arch(arm) || arch(arm64) || arch(arm64_32) + +@inline(__always) +var _tries: Int { + 100 +} + +#if arch(arm) + +// The following are acceptable operands to the aarch64 hint intrinsic from +// 'llvm-project/llvm/lib/Target/ARM/ARMInstrInfo.td': +// +// `nop` = 0 +// `yield` = 1 +// `wfe` = 2 +// `wfi` = 3 +// `sev` = 4 +// `sevl` = 5 +// +// There are others, but for the sake of spin loops, we only care about 'wfe'. +@_extern(c, "llvm.arm.hint") +func _hint(_: UInt32) + +#else + +// The following are acceptable operands to the aarch64 hint intrinsic from +// 'llvm-project/llvm/lib/Target/AArch64/AArch64InstrInfo.td': +// +// `nop` = 0 +// `yield` = 1 +// `wfe` = 2 +// `wfi` = 3 +// `sev` = 4 +// `sevl` = 5 +// +// There are others, but for the sake of spin loops, we only care about 'wfe'. +@_extern(c, "llvm.aarch64.hint") +func _hint(_: UInt32) + +#endif + +@inline(__always) +func _wfe() { + _hint(2) +} + +#elseif arch(i386) || arch(x86_64) + +@inline(__always) +var _tries: Int { + 1000 +} + +@_extern(c, "llvm.x86.sse2.pause") +func _pause() + +#else + +@inline(__always) +var _tries: Int { + 100 +} + +#endif + +@inline(__always) +func _spinLoopHint() { +#if arch(arm) || arch(arm64) || arch(arm64_32) + _wfe() +#elseif arch(i386) || arch(x86_64) + _pause() +#else + // Just do a nop on architectures we don't know about. +#endif +} diff --git a/stdlib/public/Synchronization/Mutex/WasmImpl.swift b/stdlib/public/Synchronization/Mutex/WasmImpl.swift new file mode 100644 index 0000000000000..807eb3d8c64a9 --- /dev/null +++ b/stdlib/public/Synchronization/Mutex/WasmImpl.swift @@ -0,0 +1,150 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Atomics open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +// Note: All atomic accesses on WASM are sequentially consistent regardless of +// what ordering we tell LLVM to use. + +@_extern(c, "llvm.wasm32.memory.atomic.wait32") +internal func _swift_stdlib_wait( + on: UnsafePointer, + expected: UInt32, + timeout: Int64 +) -> UInt32 + +@_extern(c, "llvm.wasm32.memory.atomic.notify") +internal func _swift_stdlib_wake(on: UnsafePointer, count: UInt32) + +extension Atomic where Value == UInt32 { + internal borrowing func _wait(expected: _MutexHandle.State) { + _swift_stdlib_wait( + on: .init(_rawAddress), + expected: expected.rawValue, + + // A timeout of < 0 means indefinitely. + timeout: -1 + ) + } + + internal borrowing func _wake() { + // Only wake up 1 thread + _swift_stdlib_wake(on: .init(_rawAddress), count: 1) + } +} + +@available(SwiftStdlib 6.0, *) +extension _MutexHandle { + @available(SwiftStdlib 6.0, *) + @frozen + @usableFromInline + internal enum State: UInt32, AtomicRepresentable { + case unlocked + case locked + case contended + } +} + +@available(SwiftStdlib 6.0, *) +@frozen +@_staticExclusiveOnly +public struct _MutexHandle: ~Copyable { + @usableFromInline + let storage: Atomic + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + public init() { + storage = Atomic(.unlocked) + } + + @available(SwiftStdlib 6.0, *) + @usableFromInline + internal borrowing func _lock() { + // Note: We could probably merge this cas into a do/while style loop, but we + // really want to perform the strong variant before attempting to do weak + // ones in the loop. + + var (exchanged, state) = storage.compareExchange( + expected: .unlocked, + desired: .locked, + successOrdering: .acquiring, + failureOrdering: .relaxed + ) + + if _fastPath(exchanged) { + // Locked! + return + } + + while !exchanged { + // If we're not already contended, go ahead and transition the mutex state + // into being contended. If when we do this that the value stored there + // was unlocked, then we know we unintentionally acquired the lock. A + // weird quirk that occurs if this happens is that we go directly from + // .unlocked -> .contended when in fact the lock may not be contended. + // We may be able to do another atomic access and change it to .locked if + // acquired it, but it may cause more problems than just potentially + // calling wake with no waiters. + if state != .contended, storage.exchange( + .contended, + ordering: .acquiring + ) == .unlocked { + // Locked! + return + } + + // Block until unlock has been called. This will return early if the call + // to unlock happened between attempting to acquire and attempting to + // wait while nobody else managed to acquire it yet. + storage._wait(expected: .contended) + + (exchanged, state) = storage.weakCompareExchange( + expected: .unlocked, + desired: .locked, + successOrdering: .acquiring, + failureOrdering: .relaxed + ) + } + + // Locked! + } + + @available(SwiftStdlib 6.0, *) + @usableFromInline + internal borrowing func _tryLock() -> Bool { + storage.compareExchange( + expected: .unlocked, + desired: .locked, + successOrdering: .acquiring, + failureOrdering: .relaxed + ).exchanged + } + + @available(SwiftStdlib 6.0, *) + @usableFromInline + internal borrowing func _unlock() { + // Transition our state from being either .locked or .contended to .unlocked. + // At this point the mutex is freely acquirable. If the value that was + // stored in the mutex was .locked, then no one else was waiting on this + // mutex so we can just skip trying to wake up a thread. + guard storage.exchange(.unlocked, ordering: .releasing) == .contended else { + // Unlocked! + return + } + + // Otherwise, wake up our next lucky random thread to acquire the mutex. + // (Assuming no new thread acquires the lock before it does) + storage._wake() + + // Unlocked! + } +} diff --git a/stdlib/public/Synchronization/Mutex/WindowsImpl.swift b/stdlib/public/Synchronization/Mutex/WindowsImpl.swift new file mode 100644 index 0000000000000..e97072b35c7a7 --- /dev/null +++ b/stdlib/public/Synchronization/Mutex/WindowsImpl.swift @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Atomics open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import WinSDK.core.synch + +@available(SwiftStdlib 6.0, *) +@frozen +@_staticExclusiveOnly +public struct _MutexHandle: ~Copyable { + @usableFromInline + let value: _Cell + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + public init() { + value = _Cell(SRWLOCK()) + } + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + internal borrowing func _lock() { + AcquireSRWLockExclusive(value._address) + } + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + internal borrowing func _tryLock() -> Bool { + // Windows BOOLEAN gets imported as 'UInt8'... + TryAcquireSRWLockExclusive(value._address) != 0 + } + + @available(SwiftStdlib 6.0, *) + @_alwaysEmitIntoClient + @_transparent + internal borrowing func _unlock() { + ReleaseSRWLockExclusive(value._address) + } +} diff --git a/test/abi/macOS/arm64/synchronization.swift b/test/abi/macOS/arm64/synchronization.swift index 03389f35f9481..15f08878ca8e8 100644 --- a/test/abi/macOS/arm64/synchronization.swift +++ b/test/abi/macOS/arm64/synchronization.swift @@ -655,3 +655,33 @@ Added: _$ss7UInt128V15Synchronization19AtomicRepresentableACMc // protocol witness table for Swift.UInt128 : Synchronization.AtomicRepresentable in Synchronization Added: _$ss7UInt128V15Synchronization19AtomicRepresentableACWP + +// Synchronization._MutexHandle.value.read : Synchronization._Cell<__C.os_unfair_lock_s> +Added: _$s15Synchronization12_MutexHandleV5valueAA5_CellVySo16os_unfair_lock_sVGvr + +// type metadata accessor for Synchronization._MutexHandle +Added: _$s15Synchronization12_MutexHandleVMa + +// nominal type descriptor for Synchronization._MutexHandle +Added: _$s15Synchronization12_MutexHandleVMn + +// type metadata for Synchronization._MutexHandle +Added: _$s15Synchronization12_MutexHandleVN + +// (extension in Synchronization):Synchronization.Mutex< where A: ~Swift.Copyable>.value.read : Synchronization._Cell +Added: _$s15Synchronization5MutexVAARi_zrlE5valueAA5_CellVyxGvr + +// (extension in Synchronization):Synchronization.Mutex< where A: ~Swift.Copyable>.handle.read : Synchronization._MutexHandle +Added: _$s15Synchronization5MutexVAARi_zrlE6handleAA01_B6HandleVvr + +// type metadata accessor for Synchronization.Mutex +Added: _$s15Synchronization5MutexVMa + +// nominal type descriptor for Synchronization.Mutex +Added: _$s15Synchronization5MutexVMn + +// type metadata accessor for Synchronization._Cell +Added: _$s15Synchronization5_CellVMa + +// nominal type descriptor for Synchronization._Cell +Added: _$s15Synchronization5_CellVMn diff --git a/test/abi/macOS/x86_64/synchronization.swift b/test/abi/macOS/x86_64/synchronization.swift index add694a0c07b2..248985a025f98 100644 --- a/test/abi/macOS/x86_64/synchronization.swift +++ b/test/abi/macOS/x86_64/synchronization.swift @@ -649,3 +649,33 @@ Added: _$ss7UInt128V15Synchronization19AtomicRepresentableACMc // protocol witness table for Swift.UInt128 : Synchronization.AtomicRepresentable in Synchronization Added: _$ss7UInt128V15Synchronization19AtomicRepresentableACWP + +// Synchronization._MutexHandle.value.read : Synchronization._Cell<__C.os_unfair_lock_s> +Added: _$s15Synchronization12_MutexHandleV5valueAA5_CellVySo16os_unfair_lock_sVGvr + +// type metadata accessor for Synchronization._MutexHandle +Added: _$s15Synchronization12_MutexHandleVMa + +// nominal type descriptor for Synchronization._MutexHandle +Added: _$s15Synchronization12_MutexHandleVMn + +// type metadata for Synchronization._MutexHandle +Added: _$s15Synchronization12_MutexHandleVN + +// (extension in Synchronization):Synchronization.Mutex< where A: ~Swift.Copyable>.value.read : Synchronization._Cell +Added: _$s15Synchronization5MutexVAARi_zrlE5valueAA5_CellVyxGvr + +// (extension in Synchronization):Synchronization.Mutex< where A: ~Swift.Copyable>.handle.read : Synchronization._MutexHandle +Added: _$s15Synchronization5MutexVAARi_zrlE6handleAA01_B6HandleVvr + +// type metadata accessor for Synchronization.Mutex +Added: _$s15Synchronization5MutexVMa + +// nominal type descriptor for Synchronization.Mutex +Added: _$s15Synchronization5MutexVMn + +// type metadata accessor for Synchronization._Cell +Added: _$s15Synchronization5_CellVMa + +// nominal type descriptor for Synchronization._Cell +Added: _$s15Synchronization5_CellVMn diff --git a/test/stdlib/Atomics/AtomicLazyReference.swift b/test/stdlib/Synchronization/Atomics/AtomicLazyReference.swift similarity index 100% rename from test/stdlib/Atomics/AtomicLazyReference.swift rename to test/stdlib/Synchronization/Atomics/AtomicLazyReference.swift diff --git a/test/stdlib/Atomics/Basics/AtomicBool.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicBool.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicBool.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicBool.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicInt.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicInt.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicInt.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicInt.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicInt16.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicInt16.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicInt16.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicInt16.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicInt32.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicInt32.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicInt32.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicInt32.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicInt64.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicInt64.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicInt64.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicInt64.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicInt8.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicInt8.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicInt8.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicInt8.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicMutablePointer.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicMutablePointer.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicMutablePointer.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicMutablePointer.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicMutableRawPointer.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicMutableRawPointer.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicMutableRawPointer.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicMutableRawPointer.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicOptionalMutablePointer.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicOptionalMutablePointer.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicOptionalMutablePointer.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicOptionalMutablePointer.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicOptionalMutableRawPointer.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicOptionalMutableRawPointer.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicOptionalMutableRawPointer.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicOptionalMutableRawPointer.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicOptionalPointer.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicOptionalPointer.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicOptionalPointer.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicOptionalPointer.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicOptionalRawPointer.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicOptionalRawPointer.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicOptionalRawPointer.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicOptionalRawPointer.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicOptionalRawRepresentable.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicOptionalRawRepresentable.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicOptionalRawRepresentable.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicOptionalRawRepresentable.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicOptionalUnmanaged.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicOptionalUnmanaged.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicOptionalUnmanaged.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicOptionalUnmanaged.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicPointer.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicPointer.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicPointer.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicPointer.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicRawPointer.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicRawPointer.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicRawPointer.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicRawPointer.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicRawRepresentable.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicRawRepresentable.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicRawRepresentable.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicRawRepresentable.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicUInt.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicUInt.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicUInt.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicUInt.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicUInt16.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicUInt16.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicUInt16.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicUInt16.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicUInt32.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicUInt32.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicUInt32.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicUInt32.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicUInt64.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicUInt64.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicUInt64.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicUInt64.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicUInt8.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicUInt8.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicUInt8.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicUInt8.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicUnmanaged.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicUnmanaged.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicUnmanaged.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicUnmanaged.swift.gyb diff --git a/test/stdlib/Atomics/Basics/AtomicWordPair.swift.gyb b/test/stdlib/Synchronization/Atomics/Basics/AtomicWordPair.swift.gyb similarity index 100% rename from test/stdlib/Atomics/Basics/AtomicWordPair.swift.gyb rename to test/stdlib/Synchronization/Atomics/Basics/AtomicWordPair.swift.gyb diff --git a/test/stdlib/Atomics/Basics/Tests.gyb-template b/test/stdlib/Synchronization/Atomics/Basics/Tests.gyb-template similarity index 100% rename from test/stdlib/Atomics/Basics/Tests.gyb-template rename to test/stdlib/Synchronization/Atomics/Basics/Tests.gyb-template diff --git a/test/stdlib/Atomics/LockFreeSingleConsumerStack.swift b/test/stdlib/Synchronization/Atomics/LockFreeSingleConsumerStack.swift similarity index 100% rename from test/stdlib/Atomics/LockFreeSingleConsumerStack.swift rename to test/stdlib/Synchronization/Atomics/LockFreeSingleConsumerStack.swift diff --git a/test/stdlib/Atomics/WordPair.swift b/test/stdlib/Synchronization/Atomics/WordPair.swift similarity index 100% rename from test/stdlib/Atomics/WordPair.swift rename to test/stdlib/Synchronization/Atomics/WordPair.swift diff --git a/test/stdlib/Synchronization/Mutex/LockSingleConsumerStack.swift b/test/stdlib/Synchronization/Mutex/LockSingleConsumerStack.swift new file mode 100644 index 0000000000000..0d2fe551de2ed --- /dev/null +++ b/test/stdlib/Synchronization/Mutex/LockSingleConsumerStack.swift @@ -0,0 +1,145 @@ +// RUN: %target-run-simple-swift(%import-libdispatch) +// REQUIRES: executable_test +// REQUIRES: libdispatch +// REQUIRES: synchronization + +import Synchronization +import StdlibUnittest +import Dispatch + +let suite = TestSuite("LockSingleConsumerStackTests") + +@available(SwiftStdlib 6.0, *) +class LockSingleConsumerStack { + struct Node { + let value: Element + var next: UnsafeMutablePointer? + } + typealias NodePtr = UnsafeMutablePointer + + private let _last = Mutex(nil) + private let _consumerCount = Mutex(0) + private var foo = 0 + + deinit { + // Discard remaining nodes + while let _ = pop() {} + } + + // Push the given element to the top of the stack. + // It is okay to concurrently call this in an arbitrary number of threads. + func push(_ value: Element) { + let new = NodePtr.allocate(capacity: 1) + new.initialize(to: Node(value: value, next: nil)) + + _last.withLock { + new.pointee.next = $0 + $0 = new + } + } + + // Pop and return the topmost element from the stack. + // This method does not support multiple overlapping concurrent calls. + func pop() -> Element? { + precondition( + _consumerCount.withLock { + let old = $0 + $0 += 1 + return old == 0 + }, + "Multiple consumers detected") + + defer { + _consumerCount.withLock { + $0 -= 1 + } + } + + return _last.withLock { (c: inout NodePtr?) -> Element? in + guard let current = c else { + return nil + } + + c = current.pointee.next + + let result = current.move() + current.deallocate() + return result.value + } + } +} + +if #available(SwiftStdlib 6.0, *) { + +suite.test("basics") { + let stack = LockSingleConsumerStack() + expectNil(stack.pop()) + stack.push(0) + expectEqual(0, stack.pop()) + + stack.push(1) + stack.push(2) + stack.push(3) + stack.push(4) + expectEqual(4, stack.pop()) + expectEqual(3, stack.pop()) + expectEqual(2, stack.pop()) + expectEqual(1, stack.pop()) + expectNil(stack.pop()) +} + +suite.test("concurrentPushes") { + let stack = LockSingleConsumerStack<(thread: Int, value: Int)>() + + let numThreads = 100 + let numValues = 10_000 + DispatchQueue.concurrentPerform(iterations: numThreads) { thread in + for value in 1 ... numValues { + stack.push((thread: thread, value: value)) + } + } + + var expected: [Int] = Array(repeating: numValues, count: numThreads) + while let (thread, value) = stack.pop() { + expectEqual(expected[thread], value) + expected[thread] -= 1 + } + expectEqual(Array(repeating: 0, count: numThreads), expected) +} + +suite.test("concurrentPushesAndPops") { + let stack = LockSingleConsumerStack<(thread: Int, value: Int)>() + + let numThreads = 100 + let numValues = 10_000 + + var perThreadSums: [Int] = Array(repeating: 0, count: numThreads) + let consumerQueue = DispatchQueue(label: "org.swift.background") + consumerQueue.async { + var count = 0 + while count < numThreads * numValues { + // Note: busy wait + if let (thread, value) = stack.pop() { + perThreadSums[thread] += value + count += 1 + } + } + } + + DispatchQueue.concurrentPerform(iterations: numThreads + 1) { thread in + if thread < numThreads { + // Producers + for value in 0 ..< numValues { + stack.push((thread: thread, value: value)) + } + } + } + + consumerQueue.sync { + expectEqual(Array(repeating: numValues * (numValues - 1) / 2, count: numThreads), perThreadSums) + } +} + +} // if #available(SwiftStdlib 6.0) + +runAllTests()