Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enable AtomicReference everywhere #65

Merged
merged 5 commits into from
Mar 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,6 @@ Of particular note is full support for atomic strong references. This provides a

All atomic operations exposed by this package are guaranteed to have lock-free implementations. However, we do not guarantee wait-free operation -- depending on the capabilities of the target platform, some of the exposed operations may be implemented by compare-and-exchange loops. That said, all atomic operations map directly to dedicated CPU instructions where available -- to the extent supported by llvm & Clang.

### Portability Concerns

Lock-free double-wide atomics requires support for such things from the underlying target platform. Where such support isn't available, this package doesn't implement `DoubleWord` atomics or atomic strong references. While modern multiprocessing CPUs have been providing double-wide atomic instructions for a number of years now, some platforms still target older architectures by default; these require a special compiler option to enable double-wide atomic instructions. This currently includes Linux operating systems running on x86_64 processors, where the `cmpxchg16b` instruction isn't considered a baseline requirement.

To enable double-wide atomics on Linux/x86_64, you currently have to manually supply a couple of additional options on the SPM build invocation:

```
$ swift build -Xcc -mcx16 -Xswiftc -DENABLE_DOUBLEWIDE_ATOMICS -c release
```

(`-mcx16` turns on support for `cmpxchg16b` in Clang, and `-DENABLE_DOUBLEWIDE_ATOMICS` makes Swift aware that double-wide atomics are available. Note that the resulting binaries won't run on some older AMD64 CPUs.)

The package cannot currently configure this automatically.

### Memory Management

Atomic access is implemented in terms of dedicated atomic storage representations that are kept distinct from the corresponding regular (non-atomic) type. (E.g., the actual integer value underlying the counter above isn't directly accessible.) This has several advantages:
Expand Down
6 changes: 0 additions & 6 deletions Sources/Atomics/AtomicStrongReference.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@

import _AtomicsShims

// Double-wide atomic primitives on x86_64 CPUs aren't available by default
// on Linux distributions, and we cannot currently enable them automatically.
#if !(os(Linux) && arch(x86_64)) || ENABLE_DOUBLEWIDE_ATOMICS

/// A class type that supports atomic strong references.
public protocol AtomicReference: AnyObject, AtomicOptionalWrappable
where
Expand Down Expand Up @@ -571,5 +567,3 @@ extension AtomicOptionalReferenceStorage: AtomicStorage {
return (result.exchanged, unsafeDowncast(original, to: Instance.self))
}
}

#endif // ENABLE_DOUBLEWIDE_ATOMICS
2 changes: 0 additions & 2 deletions Sources/Atomics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ add_library(Atomics
DoubleWord.swift)
set_target_properties(Atomics PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
target_compile_definitions(Atomics PUBLIC
ENABLE_DOUBLEWIDE_ATOMICS)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
target_compile_options(Atomics PUBLIC
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc -mcx16>")
Expand Down
10 changes: 1 addition & 9 deletions Sources/Atomics/IntegerConformances.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@ ${autogenerated_warning()}
import _AtomicsShims

% for swiftType in atomicTypes():
% if swiftType == "DoubleWord":
// Double-wide atomic primitives on x86_64 CPUs aren't available by default
// on Linux distributions, and we cannot currently enable them automatically.
#if !(os(Linux) && arch(x86_64)) || ENABLE_DOUBLEWIDE_ATOMICS
% end
extension ${swiftType}: AtomicValue {
@frozen
public struct AtomicRepresentation {
Expand Down Expand Up @@ -255,9 +250,6 @@ extension ${swiftType}.AtomicRepresentation: AtomicIntegerStorage {
}
% end
}
% end

lorentey marked this conversation as resolved.
Show resolved Hide resolved
% if swiftType == "DoubleWord":
#endif // ENABLE_DOUBLEWIDE_ATOMICS
% end
% end
% end
5 changes: 0 additions & 5 deletions Sources/Atomics/autogenerated/IntegerConformances.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4949,9 +4949,6 @@ extension UInt8.AtomicRepresentation: AtomicIntegerStorage {
}
}

// Double-wide atomic primitives on x86_64 CPUs aren't available by default
// on Linux distributions, and we cannot currently enable them automatically.
#if !(os(Linux) && arch(x86_64)) || ENABLE_DOUBLEWIDE_ATOMICS
extension DoubleWord: AtomicValue {
@frozen
public struct AtomicRepresentation {
Expand Down Expand Up @@ -5299,5 +5296,3 @@ extension DoubleWord.AtomicRepresentation: AtomicStorage {
}
}


#endif // ENABLE_DOUBLEWIDE_ATOMICS
2 changes: 0 additions & 2 deletions Sources/_AtomicsShims/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ add_library(_AtomicsShims STATIC
src/_AtomicsShims.c)
target_include_directories(_AtomicsShims PUBLIC
include)
target_compile_definitions(_AtomicsShims PUBLIC
ENABLE_DOUBLEWIDE_ATOMICS)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
target_compile_options(_AtomicsShims PUBLIC
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc -mcx16>")
Expand Down
23 changes: 0 additions & 23 deletions Sources/_AtomicsShims/include/_AtomicsShims.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,23 +62,6 @@
// (see https://github.com/apple/swift-atomics/issues/37).
#if defined(__swift__)
# include <stdatomic.h>
#endif

// For now, assume double-wide atomics are available everywhere,
// except on Linux/x86_64, where they need to be manually enabled
// by the `cx16` target attribute. (Unfortunately we cannot currently
// turn that on in our package description.)
#ifdef __APPLE__
# define ENABLE_DOUBLEWIDE_ATOMICS 1
#elif defined(_WIN32)
# define ENABLE_DOUBLEWIDE_ATOMICS 1
#elif defined(__linux__)
# if !defined(__x86_64__) || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16)
# define ENABLE_DOUBLEWIDE_ATOMICS 1
# endif
#endif

#if defined(__swift__)

#define SWIFTATOMIC_INLINE static inline __attribute__((__always_inline__))
#define SWIFTATOMIC_SWIFT_NAME(name) __attribute__((swift_name(#name)))
Expand Down Expand Up @@ -347,19 +330,13 @@ _sa_dword _sa_decode_dword(_sa_double_word_ctype value) {
return (_sa_dword){ value };
}

#if ENABLE_DOUBLEWIDE_ATOMICS
#define SWIFTATOMIC_ENCODE_DoubleWord(_value) (_value).value
#define SWIFTATOMIC_DECODE_DoubleWord(_value) _sa_decode_dword(_value)
SWIFTATOMIC_DEFINE_TYPE(COMPLEX, DoubleWord, _sa_dword, _sa_double_word_ctype)
#else
SWIFTATOMIC_STORAGE_TYPE(DoubleWord, _sa_dword, _sa_double_word_ctype)
#endif

#endif // __swift__

#if ENABLE_DOUBLEWIDE_ATOMICS
extern void _sa_retain_n(void *object, uint32_t n);
extern void _sa_release_n(void *object, uint32_t n);
#endif

#endif //SWIFTATOMIC_HEADER_INCLUDED
2 changes: 0 additions & 2 deletions Sources/_AtomicsShims/src/_AtomicsShims.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

#include "_AtomicsShims.h"

#if ENABLE_DOUBLEWIDE_ATOMICS
// FIXME: These should be static inline header-only shims, but Swift 5.3 doesn't
// like calls to swift_retain_n/swift_release_n appearing in Swift code, not
// even when imported through C. (See https://bugs.swift.org/browse/SR-13708)
Expand All @@ -28,4 +27,3 @@ void _sa_release_n(void *object, uint32_t n)
extern void swift_release_n(void *object, uint32_t n);
swift_release_n(object, n);
}
#endif
46 changes: 19 additions & 27 deletions Tests/AtomicsTests/Basics.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -76,28 +76,21 @@ private class Bar: Equatable, CustomStringConvertible {
}
}

#if !(os(Linux) && arch(x86_64)) || ENABLE_DOUBLEWIDE_ATOMICS
private class Baz: Equatable, CustomStringConvertible, AtomicReference {
private final class Baz: Equatable, CustomStringConvertible, AtomicReference {
var value: Int
init(_ value: Int) { self.value = value }
var description: String { "Bar(\(value))" }
static func ==(left: Baz, right: Baz) -> Bool {
left === right
}
}
#endif

private enum Fred: Int, AtomicValue {
case one
case two
}

% for label, type, a, b in types:
% if label == "DoubleWord" or label == "Reference" or label == "OptionalReference":
#if !(os(Linux) && arch(x86_64)) || ENABLE_DOUBLEWIDE_ATOMICS
% else:
#if true
% end
/// Exercises all operations in a single-threaded context, verifying
/// they provide the expected results.
class BasicAtomic${label}Tests: XCTestCase {
Expand Down Expand Up @@ -148,35 +141,35 @@ class BasicAtomic${label}Tests: XCTestCase {
% end

func test_create_destroy() {
let v = UnsafeAtomic<${type}>.create(${a})
let v: UnsafeAtomic<${type}> = .create(${a})
defer { v.destroy() }
XCTAssertEqual(v.load(ordering: .relaxed), ${a})

let w = UnsafeAtomic<${type}>.create(${b})
let w: UnsafeAtomic<${type}> = .create(${b})
defer { w.destroy() }
XCTAssertEqual(w.load(ordering: .relaxed), ${b})
}

% for (order, _) in loadOrderings:
func test_load_${order}() {
let v = UnsafeAtomic<${type}>.create(${a})
let v: UnsafeAtomic<${type}> = .create(${a})
defer { v.destroy() }
XCTAssertEqual(v.load(ordering: .${order}), ${a})

let w = UnsafeAtomic<${type}>.create(${b})
let w: UnsafeAtomic<${type}> = .create(${b})
defer { w.destroy() }
XCTAssertEqual(w.load(ordering: .${order}), ${b})
}
% end

% for (order, _) in storeOrderings:
func test_store_${order}() {
let v = UnsafeAtomic<${type}>.create(${a})
let v: UnsafeAtomic<${type}> = .create(${a})
defer { v.destroy() }
v.store(${b}, ordering: .${order})
XCTAssertEqual(v.load(ordering: .relaxed), ${b})

let w = UnsafeAtomic<${type}>.create(${b})
let w: UnsafeAtomic<${type}> = .create(${b})
defer { w.destroy() }
w.store(${a}, ordering: .${order})
XCTAssertEqual(w.load(ordering: .relaxed), ${a})
Expand All @@ -185,7 +178,7 @@ class BasicAtomic${label}Tests: XCTestCase {

% for (order, _, _) in updateOrderings:
func test_exchange_${order}() {
let v = UnsafeAtomic<${type}>.create(${a})
let v: UnsafeAtomic<${type}> = .create(${a})
defer { v.destroy() }

XCTAssertEqual(v.exchange(${a}, ordering: .${order}), ${a})
Expand All @@ -201,10 +194,10 @@ class BasicAtomic${label}Tests: XCTestCase {

% for (order, _, _) in updateOrderings:
func test_compareExchange_${order}() {
let v = UnsafeAtomic<${type}>.create(${a})
let v: UnsafeAtomic<${type}> = .create(${a})
defer { v.destroy() }

var (exchanged, original) = v.compareExchange(
var (exchanged, original): (Bool, ${type}) = v.compareExchange(
expected: ${a},
desired: ${b},
ordering: .${order})
Expand Down Expand Up @@ -242,10 +235,10 @@ class BasicAtomic${label}Tests: XCTestCase {
% for (successorder, _, _) in updateOrderings:
% for (failorder, _) in loadOrderings:
func test_${operation}_${successorder}_${failorder}() {
let v = UnsafeAtomic<${type}>.create(${a})
let v: UnsafeAtomic<${type}> = .create(${a})
defer { v.destroy() }

var (exchanged, original) = v.compareExchange(
var (exchanged, original): (Bool, ${type}) = v.compareExchange(
expected: ${a},
desired: ${b},
successOrdering: .${successorder},
Expand Down Expand Up @@ -346,14 +339,14 @@ class BasicAtomic${label}Tests: XCTestCase {
let result1: ${type} = a ${operator} b
let result2: ${type} = result1 ${operator} c

let v = UnsafeAtomic<${type}>.create(a)
let v: UnsafeAtomic<${type}> = .create(a)
defer { v.destroy() }

let old1 = v.loadThen${name}(${argLabel(arglabel)}b, ordering: .${order})
let old1: ${type} = v.loadThen${name}(${argLabel(arglabel)}b, ordering: .${order})
XCTAssertEqual(old1, a)
XCTAssertEqual(v.load(ordering: .relaxed), result1)

let old2 = v.loadThen${name}(${argLabel(arglabel)}c, ordering: .${order})
let old2: ${type} = v.loadThen${name}(${argLabel(arglabel)}c, ordering: .${order})
XCTAssertEqual(old2, result1)
XCTAssertEqual(v.load(ordering: .relaxed), result2)
}
Expand All @@ -369,14 +362,14 @@ class BasicAtomic${label}Tests: XCTestCase {
let result1: ${type} = a ${operator} b
let result2: ${type} = result1 ${operator} c

let v = UnsafeAtomic<${type}>.create(a)
let v: UnsafeAtomic<${type}> = .create(a)
defer { v.destroy() }

let new1 = v.${lowerFirst(name)}ThenLoad(${argLabel(arglabel)}b, ordering: .${order})
let new1: ${type} = v.${lowerFirst(name)}ThenLoad(${argLabel(arglabel)}b, ordering: .${order})
XCTAssertEqual(new1, result1)
XCTAssertEqual(v.load(ordering: .relaxed), result1)

let new2 = v.${lowerFirst(name)}ThenLoad(${argLabel(arglabel)}c, ordering: .${order})
let new2: ${type} = v.${lowerFirst(name)}ThenLoad(${argLabel(arglabel)}c, ordering: .${order})
XCTAssertEqual(new2, result2)
XCTAssertEqual(v.load(ordering: .relaxed), result2)
}
Expand All @@ -385,7 +378,7 @@ class BasicAtomic${label}Tests: XCTestCase {
% end


public static var allTests = [
public static var allTests: [(String, (BasicAtomic${label}Tests) -> () -> ())] = [
("test_create_destroy", test_create_destroy),
% for (order, _) in loadOrderings:
("test_load_${order}", test_load_${order}),
Expand Down Expand Up @@ -432,5 +425,4 @@ class BasicAtomic${label}Tests: XCTestCase {
% end
]
}
#endif
% end
5 changes: 1 addition & 4 deletions Tests/AtomicsTests/LockFreeQueue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import XCTest
import Dispatch
import Atomics

#if !(os(Linux) && arch(x86_64)) || ENABLE_DOUBLEWIDE_ATOMICS
private var iterations: Int {
#if SWIFT_ATOMICS_LONG_TESTS
return 1_000_000
Expand All @@ -40,7 +39,7 @@ private var iterations: Int {
private let nodeCount = ManagedAtomic<Int>(0)

class LockFreeQueue<Element> {
class Node: AtomicReference {
final class Node: AtomicReference {
let next: ManagedAtomic<Node?>
var value: Element?

Expand Down Expand Up @@ -194,5 +193,3 @@ class QueueTests: XCTestCase {
]
#endif
}

#endif
4 changes: 1 addition & 3 deletions Tests/AtomicsTests/StrongReferenceRace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import XCTest
import Atomics
import Dispatch

#if !(os(Linux) && arch(x86_64)) || ENABLE_DOUBLEWIDE_ATOMICS
private var iterations: Int {
#if SWIFT_ATOMICS_LONG_TESTS
return 1_000_000
Expand All @@ -23,7 +22,7 @@ private var iterations: Int {
#endif
}

private class Node: AtomicReference {
private final class Node: AtomicReference {
let value: Int

init(_ value: Int = 0) {
Expand Down Expand Up @@ -288,4 +287,3 @@ class StrongReferenceRace: XCTestCase {
]
#endif
}
#endif
4 changes: 1 addition & 3 deletions Tests/AtomicsTests/StrongReferenceShuffle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import XCTest
import Atomics
import Dispatch

#if !(os(Linux) && arch(x86_64)) || ENABLE_DOUBLEWIDE_ATOMICS
private var iterations: Int {
#if SWIFT_ATOMICS_LONG_TESTS
return 1_000_000
Expand All @@ -31,7 +30,7 @@ private var iterations: Int {
private let nodeCount = ManagedAtomic<Int>(0)

private class List<Value: Equatable> {
class Node: AtomicReference {
final class Node: AtomicReference {
private let _next: UnsafeAtomic<Node?>
private let _value: UnsafeAtomic<UnsafeMutablePointer<Value>?>

Expand Down Expand Up @@ -290,4 +289,3 @@ class StrongReferenceShuffleTests: XCTestCase {
]
#endif
}
#endif
Loading