From 6c5358210d0b48648943ef94aa9a7b4ab7801cbc Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 27 Jul 2023 13:20:51 -0700 Subject: [PATCH] Introduce a noncopyable Atomic construct --- Package.swift | 3 +- Sources/Atomics/Atomic.swift | 487 ++++++++++++++++++ Sources/Atomics/AtomicBool.swift.gyb | 2 +- .../Basics/BasicTests.gyb-template | 39 +- 4 files changed, 503 insertions(+), 28 deletions(-) create mode 100644 Sources/Atomics/Atomic.swift diff --git a/Package.swift b/Package.swift index 62022fa..ecad074 100644 --- a/Package.swift +++ b/Package.swift @@ -22,7 +22,8 @@ _cSettings += [ ] _swiftSettings += [ .define("ATOMICS_NATIVE_BUILTINS"), - .enableExperimentalFeature("BuiltinModule") + .enableExperimentalFeature("BuiltinModule"), + .enableExperimentalFeature("RawLayout") ] let package = Package( diff --git a/Sources/Atomics/Atomic.swift b/Sources/Atomics/Atomic.swift new file mode 100644 index 0000000..185479d --- /dev/null +++ b/Sources/Atomics/Atomic.swift @@ -0,0 +1,487 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Atomics open source project +// +// Copyright (c) 2020 - 2023 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 compiler(>=5.9) && $RawLayout +import Builtin + +// FIXME: What we actually want to say is @_rawLayout(like: Value.AtomicRepresentation) +@_rawLayout(like: DoubleWord.AtomicRepresentation) +@frozen +public struct Atomic: ~Copyable +where Value.AtomicRepresentation.Value == Value +{ + @usableFromInline + internal typealias _Storage = Value.AtomicRepresentation + + @_transparent @_alwaysEmitIntoClient + internal var _ptr: UnsafeMutablePointer<_Storage> { + .init(Builtin.addressOfBorrow(self)) + } + + public init(_ initialValue: __owned Value) { + _ptr.initialize(to: _Storage(initialValue)) + } + + deinit { + _ = _ptr.pointee.dispose() + _ptr.deinitialize(count: 1) + } +} + +extension Atomic { + /// Atomically loads and returns the current value, applying the specified + /// memory ordering. + /// + /// - Parameter ordering: The memory ordering to apply on this operation. + /// - Returns: The current value. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func load( + ordering: AtomicLoadOrdering + ) -> Value { + _Storage.atomicLoad(at: _ptr, ordering: ordering) + } + + /// Atomically sets the current value to `desired`, applying the specified + /// memory ordering. + /// + /// - Parameter desired: The desired new value. + /// - Parameter ordering: The memory ordering to apply on this operation. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func store( + _ desired: __owned Value, + ordering: AtomicStoreOrdering + ) { + _Storage.atomicStore(desired, at: _ptr, ordering: ordering) + } + + /// Atomically sets the current value to `desired` and returns the original + /// value, applying the specified memory ordering. + /// + /// - Parameter desired: The desired new value. + /// - Parameter ordering: The memory ordering to apply on this operation. + /// - Returns: The original value. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func exchange( + _ desired: __owned Value, + ordering: AtomicUpdateOrdering + ) -> Value { + _Storage.atomicExchange(desired, at: _ptr, ordering: ordering) + } + + /// Perform an atomic compare and exchange operation on the current value, + /// applying the specified memory ordering. + /// + /// This operation performs the following algorithm as a single atomic + /// transaction: + /// + /// ``` + /// atomic(self) { currentValue in + /// let original = currentValue + /// guard original == expected else { return (false, original) } + /// currentValue = desired + /// return (true, original) + /// } + /// ``` + /// + /// This method implements a "strong" compare and exchange operation + /// that does not permit spurious failures. + /// + /// - Parameter expected: The expected current value. + /// - Parameter desired: The desired new value. + /// - Parameter ordering: The memory ordering to apply on this operation. + /// - Returns: A tuple `(exchanged, original)`, where `exchanged` is true if + /// the exchange was successful, and `original` is the original value. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func compareExchange( + expected: Value, + desired: __owned Value, + ordering: AtomicUpdateOrdering + ) -> (exchanged: Bool, original: Value) { + _Storage.atomicCompareExchange( + expected: expected, + desired: desired, + at: _ptr, + ordering: ordering) + } + + /// Perform an atomic compare and exchange operation on the current value, + /// applying the specified success/failure memory orderings. + /// + /// This operation performs the following algorithm as a single atomic + /// transaction: + /// + /// ``` + /// atomic(self) { currentValue in + /// let original = currentValue + /// guard original == expected else { return (false, original) } + /// currentValue = desired + /// return (true, original) + /// } + /// ``` + /// + /// The `successOrdering` argument specifies the memory ordering to use when + /// the operation manages to update the current value, while `failureOrdering` + /// will be used when the operation leaves the value intact. + /// + /// This method implements a "strong" compare and exchange operation + /// that does not permit spurious failures. + /// + /// - Parameter expected: The expected current value. + /// - Parameter desired: The desired new value. + /// - Parameter successOrdering: The memory ordering to apply if this + /// operation performs the exchange. + /// - Parameter failureOrdering: The memory ordering to apply on this + /// operation does not perform the exchange. + /// - Returns: A tuple `(exchanged, original)`, where `exchanged` is true if + /// the exchange was successful, and `original` is the original value. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func compareExchange( + expected: Value, + desired: __owned Value, + successOrdering: AtomicUpdateOrdering, + failureOrdering: AtomicLoadOrdering + ) -> (exchanged: Bool, original: Value) { + _Storage.atomicCompareExchange( + expected: expected, + desired: desired, + at: _ptr, + successOrdering: successOrdering, + failureOrdering: failureOrdering) + } + + /// Perform an atomic weak compare and exchange operation on the current + /// value, applying the memory ordering. This compare-exchange variant is + /// allowed to spuriously fail; it is designed to be called in a loop until + /// it indicates a successful exchange has happened. + /// + /// This operation performs the following algorithm as a single atomic + /// transaction: + /// + /// ``` + /// atomic(self) { currentValue in + /// let original = currentValue + /// guard original == expected else { return (false, original) } + /// currentValue = desired + /// return (true, original) + /// } + /// ``` + /// + /// (In this weak form, transient conditions may cause the `original == + /// expected` check to sometimes return false when the two values are in fact + /// the same.) + /// + /// - Parameter expected: The expected current value. + /// - Parameter desired: The desired new value. + /// - Parameter ordering: The memory ordering to apply on this operation. + /// - Returns: A tuple `(exchanged, original)`, where `exchanged` is true if + /// the exchange was successful, and `original` is the original value. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func weakCompareExchange( + expected: Value, + desired: __owned Value, + ordering: AtomicUpdateOrdering + ) -> (exchanged: Bool, original: Value) { + _Storage.atomicWeakCompareExchange( + expected: expected, + desired: desired, + at: _ptr, + ordering: ordering) + } + + /// Perform an atomic weak compare and exchange operation on the current + /// value, applying the specified success/failure memory orderings. This + /// compare-exchange variant is allowed to spuriously fail; it is designed to + /// be called in a loop until it indicates a successful exchange has happened. + /// + /// This operation performs the following algorithm as a single atomic + /// transaction: + /// + /// ``` + /// atomic(self) { currentValue in + /// let original = currentValue + /// guard original == expected else { return (false, original) } + /// currentValue = desired + /// return (true, original) + /// } + /// ``` + /// + /// (In this weak form, transient conditions may cause the `original == + /// expected` check to sometimes return false when the two values are in fact + /// the same.) + /// + /// The `ordering` argument specifies the memory ordering to use when the + /// operation manages to update the current value, while `failureOrdering` + /// will be used when the operation leaves the value intact. + /// + /// - Parameter expected: The expected current value. + /// - Parameter desired: The desired new value. + /// - Parameter successOrdering: The memory ordering to apply if this + /// operation performs the exchange. + /// - Parameter failureOrdering: The memory ordering to apply on this + /// operation does not perform the exchange. + /// - Returns: A tuple `(exchanged, original)`, where `exchanged` is true if + /// the exchange was successful, and `original` is the original value. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func weakCompareExchange( + expected: Value, + desired: __owned Value, + successOrdering: AtomicUpdateOrdering, + failureOrdering: AtomicLoadOrdering + ) -> (exchanged: Bool, original: Value) { + _Storage.atomicWeakCompareExchange( + expected: expected, + desired: desired, + at: _ptr, + successOrdering: successOrdering, + failureOrdering: failureOrdering) + } +} + +extension Atomic where Value: AtomicInteger { + /// Perform an atomic wrapping add operation and return the original value, applying + /// the specified memory ordering. + /// + /// Note: This operation silently wraps around on overflow, like the + /// `&+` operator does on `Int` values. + /// + /// - Parameter operand: An integer value. + /// - Parameter ordering: The memory ordering to apply on this operation. + /// - Returns: The original value before the operation. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func loadThenWrappingIncrement( + by operand: Value = 1, + ordering: AtomicUpdateOrdering + ) -> Value { + _Storage.atomicLoadThenWrappingIncrement( + by: operand, + at: _ptr, + ordering: ordering) + } + /// Perform an atomic wrapping subtract operation and return the original value, applying + /// the specified memory ordering. + /// + /// Note: This operation silently wraps around on overflow, like the + /// `&-` operator does on `Int` values. + /// + /// - Parameter operand: An integer value. + /// - Parameter ordering: The memory ordering to apply on this operation. + /// - Returns: The original value before the operation. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func loadThenWrappingDecrement( + by operand: Value = 1, + ordering: AtomicUpdateOrdering + ) -> Value { + _Storage.atomicLoadThenWrappingDecrement( + by: operand, + at: _ptr, + ordering: ordering) + } + /// Perform an atomic bitwise AND operation and return the original value, applying + /// the specified memory ordering. + /// + /// - Parameter operand: An integer value. + /// - Parameter ordering: The memory ordering to apply on this operation. + /// - Returns: The original value before the operation. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func loadThenBitwiseAnd( + with operand: Value, + ordering: AtomicUpdateOrdering + ) -> Value { + _Storage.atomicLoadThenBitwiseAnd( + with: operand, + at: _ptr, + ordering: ordering) + } + /// Perform an atomic bitwise OR operation and return the original value, applying + /// the specified memory ordering. + /// + /// - Parameter operand: An integer value. + /// - Parameter ordering: The memory ordering to apply on this operation. + /// - Returns: The original value before the operation. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func loadThenBitwiseOr( + with operand: Value, + ordering: AtomicUpdateOrdering + ) -> Value { + _Storage.atomicLoadThenBitwiseOr( + with: operand, + at: _ptr, + ordering: ordering) + } + /// Perform an atomic bitwise XOR operation and return the original value, applying + /// the specified memory ordering. + /// + /// - Parameter operand: An integer value. + /// - Parameter ordering: The memory ordering to apply on this operation. + /// - Returns: The original value before the operation. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func loadThenBitwiseXor( + with operand: Value, + ordering: AtomicUpdateOrdering + ) -> Value { + _Storage.atomicLoadThenBitwiseXor( + with: operand, + at: _ptr, + ordering: ordering) + } + + /// Perform an atomic wrapping add operation and return the new value, applying + /// the specified memory ordering. + /// + /// Note: This operation silently wraps around on overflow, like the + /// `&+` operator does on `Int` values. + /// + /// - Parameter operand: An integer value. + /// - Parameter ordering: The memory ordering to apply on this operation. + /// - Returns: The new value after the operation. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func wrappingIncrementThenLoad( + by operand: Value = 1, + ordering: AtomicUpdateOrdering + ) -> Value { + let original = _Storage.atomicLoadThenWrappingIncrement( + by: operand, + at: _ptr, + ordering: ordering) + return original &+ operand + } + /// Perform an atomic wrapping subtract operation and return the new value, applying + /// the specified memory ordering. + /// + /// Note: This operation silently wraps around on overflow, like the + /// `&-` operator does on `Int` values. + /// + /// - Parameter operand: An integer value. + /// - Parameter ordering: The memory ordering to apply on this operation. + /// - Returns: The new value after the operation. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func wrappingDecrementThenLoad( + by operand: Value = 1, + ordering: AtomicUpdateOrdering + ) -> Value { + let original = _Storage.atomicLoadThenWrappingDecrement( + by: operand, + at: _ptr, + ordering: ordering) + return original &- operand + } + /// Perform an atomic bitwise AND operation and return the new value, applying + /// the specified memory ordering. + /// + /// - Parameter operand: An integer value. + /// - Parameter ordering: The memory ordering to apply on this operation. + /// - Returns: The new value after the operation. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func bitwiseAndThenLoad( + with operand: Value, + ordering: AtomicUpdateOrdering + ) -> Value { + let original = _Storage.atomicLoadThenBitwiseAnd( + with: operand, + at: _ptr, + ordering: ordering) + return original & operand + } + /// Perform an atomic bitwise OR operation and return the new value, applying + /// the specified memory ordering. + /// + /// - Parameter operand: An integer value. + /// - Parameter ordering: The memory ordering to apply on this operation. + /// - Returns: The new value after the operation. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func bitwiseOrThenLoad( + with operand: Value, + ordering: AtomicUpdateOrdering + ) -> Value { + let original = _Storage.atomicLoadThenBitwiseOr( + with: operand, + at: _ptr, + ordering: ordering) + return original | operand + } + /// Perform an atomic bitwise XOR operation and return the new value, applying + /// the specified memory ordering. + /// + /// - Parameter operand: An integer value. + /// - Parameter ordering: The memory ordering to apply on this operation. + /// - Returns: The new value after the operation. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func bitwiseXorThenLoad( + with operand: Value, + ordering: AtomicUpdateOrdering + ) -> Value { + let original = _Storage.atomicLoadThenBitwiseXor( + with: operand, + at: _ptr, + ordering: ordering) + return original ^ operand + } + + /// Perform an atomic wrapping increment operation applying the + /// specified memory ordering. + /// + /// Note: This operation silently wraps around on overflow, like the + /// `&+=` operator does on `Int` values. + /// + /// - Parameter operand: The value to add to the current value. + /// - Parameter ordering: The memory ordering to apply on this operation. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func wrappingIncrement( + by operand: Value = 1, + ordering: AtomicUpdateOrdering + ) { + _ = _Storage.atomicLoadThenWrappingIncrement( + by: operand, + at: _ptr, + ordering: ordering) + } + + /// Perform an atomic wrapping decrement operation applying the + /// specified memory ordering. + /// + /// Note: This operation silently wraps around on overflow, like the + /// `&-=` operator does on `Int` values. + /// + /// - Parameter operand: The value to subtract from the current value. + /// - Parameter ordering: The memory ordering to apply on this operation. + @_semantics("atomics.requires_constant_orderings") + @_transparent @_alwaysEmitIntoClient + public func wrappingDecrement( + by operand: Value = 1, + ordering: AtomicUpdateOrdering + ) { + _ = _Storage.atomicLoadThenWrappingDecrement( + by: operand, + at: _ptr, + ordering: ordering) + } +} + +#endif diff --git a/Sources/Atomics/AtomicBool.swift.gyb b/Sources/Atomics/AtomicBool.swift.gyb index 5d3aae9..191b060 100644 --- a/Sources/Atomics/AtomicBool.swift.gyb +++ b/Sources/Atomics/AtomicBool.swift.gyb @@ -189,7 +189,7 @@ extension Bool.AtomicRepresentation { % end } -% for construct in ["UnsafeAtomic", "ManagedAtomic"]: +% for construct in ["Atomic", "UnsafeAtomic", "ManagedAtomic"]: extension ${construct} where Value == Bool { % for (name, iname, op, label, doc) in boolOperations: /// Perform an atomic ${doc} operation and return the original value, applying diff --git a/Tests/AtomicsTests/Basics/BasicTests.gyb-template b/Tests/AtomicsTests/Basics/BasicTests.gyb-template index 32251f1..942f10f 100644 --- a/Tests/AtomicsTests/Basics/BasicTests.gyb-template +++ b/Tests/AtomicsTests/Basics/BasicTests.gyb-template @@ -71,23 +71,19 @@ class BasicAtomic${label}Tests: XCTestCase { % end func test_create_destroy() { - let v: UnsafeAtomic<${type}> = .create(${a}) - defer { v.destroy() } + let v = Atomic<${type}>(${a}) XCTAssertEqual(v.load(ordering: .relaxed), ${a}) - let w: UnsafeAtomic<${type}> = .create(${b}) - defer { w.destroy() } + let w = Atomic<${type}>(${b}) XCTAssertEqual(w.load(ordering: .relaxed), ${b}) } % for (order, _, _, _) in loadOrderings: func test_load_${order}() { - let v: UnsafeAtomic<${type}> = .create(${a}) - defer { v.destroy() } + let v = Atomic<${type}>(${a}) XCTAssertEqual(v.load(ordering: .${order}), ${a}) - let w: UnsafeAtomic<${type}> = .create(${b}) - defer { w.destroy() } + let w = Atomic<${type}>(${b}) XCTAssertEqual(w.load(ordering: .${order}), ${b}) } @@ -95,13 +91,11 @@ class BasicAtomic${label}Tests: XCTestCase { % for (order, _, _, _) in storeOrderings: func test_store_${order}() { - let v: UnsafeAtomic<${type}> = .create(${a}) - defer { v.destroy() } + let v = Atomic<${type}>(${a}) v.store(${b}, ordering: .${order}) XCTAssertEqual(v.load(ordering: .relaxed), ${b}) - let w: UnsafeAtomic<${type}> = .create(${b}) - defer { w.destroy() } + let w = Atomic<${type}>(${b}) w.store(${a}, ordering: .${order}) XCTAssertEqual(w.load(ordering: .relaxed), ${a}) } @@ -110,8 +104,7 @@ class BasicAtomic${label}Tests: XCTestCase { % for (order, _, _, _, _) in updateOrderings: func test_exchange_${order}() { - let v: UnsafeAtomic<${type}> = .create(${a}) - defer { v.destroy() } + let v = Atomic<${type}>(${a}) XCTAssertEqual(v.exchange(${a}, ordering: .${order}), ${a}) XCTAssertEqual(v.load(ordering: .relaxed), ${a}) @@ -128,8 +121,7 @@ class BasicAtomic${label}Tests: XCTestCase { % for operation in ["compareExchange", "weakCompareExchange"]: % for (order, _, _, _, _) in updateOrderings: func test_${operation}_${order}() { - let v: UnsafeAtomic<${type}> = .create(${a}) - defer { v.destroy() } + let v = Atomic<${type}>(${a}) var (exchanged, original): (Bool, ${type}) = v.${operation}( expected: ${a}, @@ -171,8 +163,7 @@ class BasicAtomic${label}Tests: XCTestCase { % for (successorder, _, _, _, _) in updateOrderings: % for (failorder, _, _, _) in loadOrderings: func test_${operation}_${successorder}_${failorder}() { - let v: UnsafeAtomic<${type}> = .create(${a}) - defer { v.destroy() } + let v = Atomic<${type}>(${a}) var (exchanged, original): (Bool, ${type}) = v.${operation}( expected: ${a}, @@ -221,8 +212,7 @@ class BasicAtomic${label}Tests: XCTestCase { % for (name, _, operator, arglabel, _) in boolOperations: % for (order, _, _, _, _) in updateOrderings: func test_loadThen${name}_${order}() { - let v = UnsafeAtomic.create(false) - defer { v.destroy() } + let v = Atomic<${type}>(false) // The truth tables are super tiny, so just check every value for a in [false, true] { @@ -244,8 +234,7 @@ class BasicAtomic${label}Tests: XCTestCase { % for (name, _, operator, arglabel, _) in boolOperations: % for (order, _, _, _, _) in updateOrderings: func test_${lowerFirst(name)}ThenLoad_${order}() { - let v = UnsafeAtomic.create(false) - defer { v.destroy() } + let v = Atomic<${type}>(false) // The truth tables are super tiny, so just check every value for a in [false, true] { @@ -278,8 +267,7 @@ class BasicAtomic${label}Tests: XCTestCase { let result1: ${type} = a ${operator} b let result2: ${type} = result1 ${operator} c - let v: UnsafeAtomic<${type}> = .create(a) - defer { v.destroy() } + let v = Atomic<${type}>(a) let old1: ${type} = v.loadThen${name}(${argLabel(arglabel)}b, ordering: .${order}) XCTAssertEqual(old1, a) @@ -302,8 +290,7 @@ class BasicAtomic${label}Tests: XCTestCase { let result1: ${type} = a ${operator} b let result2: ${type} = result1 ${operator} c - let v: UnsafeAtomic<${type}> = .create(a) - defer { v.destroy() } + let v = Atomic<${type}>(a) let new1: ${type} = v.${lowerFirst(name)}ThenLoad(${argLabel(arglabel)}b, ordering: .${order}) XCTAssertEqual(new1, result1)