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

[stdlib] Implement Atomics module #30553

Closed
wants to merge 42 commits into from
Closed

Conversation

lorentey
Copy link
Member

@lorentey lorentey commented Mar 21, 2020

This is the implementation that goes with the Low-Level Atomic Operations pitch. For details, see there!

In this pitch, we are gathering feedback about the idea of adding a limited set of low-level atomic operations to the Standard Library, including native spellings for C++-style acquire-release memory orderings. Our goal is to enable intrepid library authors to start building synchronization constructs directly in Swift.

As a quick taste, we want to make atomics look like this:

import Atomics
import Dispatch

let counter = UnsafeAtomic<Int>.create(initialValue: 0)

DispatchQueue.concurrentPerform(iterations: 10) { _ in
  for _ in 0 ..< 1_000_000 {
    counter.wrappingIncrement(ordering: .relaxed)
  }
}
print(counter.load(ordering: .relaxed))
counter.destroy()

(This is a followup to the atomic experiments in #27229.)

Fixes #51640
rdar://17171396

@lorentey lorentey added the swift evolution pending discussion Flag → feature: A feature that has a Swift evolution proposal currently in review label Mar 21, 2020
@lorentey
Copy link
Member Author

@swift-ci test

stdlib/public/Atomics/AtomicMemoryOrderings.swift Outdated Show resolved Hide resolved
stdlib/public/Atomics/AtomicMemoryOrderings.swift Outdated Show resolved Hide resolved
stdlib/public/Atomics/CMakeLists.txt Outdated Show resolved Hide resolved
@lorentey
Copy link
Member Author

@swift-ci test

@swift-ci

This comment has been minimized.

@swift-ci

This comment has been minimized.

@lorentey
Copy link
Member Author

@swift-ci test

@lorentey
Copy link
Member Author

@swift-ci test

@lorentey
Copy link
Member Author

@swift-ci test Windows platform

@swift-ci

This comment has been minimized.

@swift-ci

This comment has been minimized.

stdlib/public/Atomics/AtomicRepresentable.swift Outdated Show resolved Hide resolved
stdlib/public/Atomics/AtomicPointers.swift.gyb Outdated Show resolved Hide resolved
stdlib/public/Atomics/AtomicProtocols.swift Outdated Show resolved Hide resolved
…& storage mgmt

- _PrimitiveAtomic is a new hidden protocol implementing the core atomic operations.
- _PrimitiveAtomicOptional refines it adding nullability.
- AtomicProtocol now consists of a _PrimitiveAtomic-constrained _AtomicStorage, plus conversion methods — all underscored.
- Pointer types have opaque atomic storage.
- User-visible atomic storage type is UnsafeAtomic.Storage — an opaque value type with a convenience initializer and a dispose() method.
@freak4pc
Copy link
Contributor

This looks great, thanks for working on it! Looking forward to seeing this added, and help avoid many hacks non-official implementations.

I do wonder though if there are any plans for Atomicity in a type-safe level, as opposed to this UnsafeAtomic implementation. Not necessarily in this implementation, but generally speaking ?

Thanks !

@lorentey
Copy link
Member Author

Yes -- the current implementation was specifically designed to allow easy transition to the eventual memory-safe constructs later. The preliminary proposal text has a section explaining what's missing.

@lorentey
Copy link
Member Author

@swift-ci test

@swift-ci
Copy link
Collaborator

Build failed
Swift Test Linux Platform
Git Sha - 01a0184

Comment on lines 15 to 25
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
public protocol _PrimitiveAtomicOptional: _PrimitiveAtomic {
var _isNil: Bool { get }
static var _nilValue: Self { get }
}

@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
extension Optional: AtomicProtocol
where Wrapped: AtomicProtocol, Wrapped._AtomicStorage: _PrimitiveAtomicOptional
{
public typealias _AtomicStorage = Wrapped._AtomicStorage
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use Equatable & ExpressibleByNilLiteral storage to eliminate the _PrimitiveAtomicOptional protocol?

  • return _AtomicStorage._nilValue ➡️ return nil
  • !storage._isNil ➡️ storage != nil
Suggested change
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
public protocol _PrimitiveAtomicOptional: _PrimitiveAtomic {
var _isNil: Bool { get }
static var _nilValue: Self { get }
}
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
extension Optional: AtomicProtocol
where Wrapped: AtomicProtocol, Wrapped._AtomicStorage: _PrimitiveAtomicOptional
{
public typealias _AtomicStorage = Wrapped._AtomicStorage
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
extension Optional: AtomicProtocol where
Wrapped: AtomicProtocol,
Wrapped._AtomicStorage: Equatable & ExpressibleByNilLiteral
{
public typealias _AtomicStorage = Wrapped._AtomicStorage

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but that doesn't look like a particularly great idea to me. I prefer the explicit un-cleverness of a dedicated protocol here.

There is also this:

nil has a specific meaning in Swift—the absence of a value. Only the Optional type conforms to ExpressibleByNilLiteral. ExpressibleByNilLiteral conformance for types that use nil for other purposes is discouraged.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also this:

nil has a specific meaning in Swift—the absence of a value. Only the Optional type conforms to ExpressibleByNilLiteral.

And the _OptionalNilComparisonType!

Copy link
Contributor

@belkadan belkadan Apr 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do think it'd be fair to say _PrimitiveAtomicOptional inherits from ExpressibleByNilLiteral though. That would still simplify the implementation.

Copy link
Member Author

@lorentey lorentey Apr 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The storage types aren't currently ExpressibleByNilLiteral, though, and I think I'd like to keep them that way.

I am ambivalent towards adding Equatable conformance to atomic storage. (For the same reason I keep fiddling with the FixedWidthInteger conformance on _PrimitiveAtomicInteger. I dislike adding too much structure to the storage types -- these should be treated as opaque boxes.)

Comment on lines +41 to +61
@frozen
public struct Storage {
@usableFromInline
internal var _value: Value._AtomicStorage

@inlinable @inline(__always)
internal init(_value: __owned Value._AtomicStorage) {
self._value = _value
}

@inlinable @inline(__always)
public init(_ value: __owned Value) {
self._value = Value._prepareAtomicStorage(for: value)
}

@inlinable @inline(__always)
@discardableResult
public mutating func dispose() -> Value {
return Value._disposeAtomicStorage(&_value)
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use a typealias to eliminate the UnsafeAtomic.Storage structure?

  • the computed var _ptr and its assumingMemoryBound(to:) wouldn't be needed.
  • possibly rename _AtomicStorage ➡️ AtomicStorage (since integers already have nested SIMDStorage types).
Suggested change
@frozen
public struct Storage {
@usableFromInline
internal var _value: Value._AtomicStorage
@inlinable @inline(__always)
internal init(_value: __owned Value._AtomicStorage) {
self._value = _value
}
@inlinable @inline(__always)
public init(_ value: __owned Value) {
self._value = Value._prepareAtomicStorage(for: value)
}
@inlinable @inline(__always)
@discardableResult
public mutating func dispose() -> Value {
return Value._disposeAtomicStorage(&_value)
}
}
public typealias Storage = Value._AtomicStorage

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this is technically possible, but I prefer to have a dedicated, public wrapper so that particular choices for _AtomicStorage won't get hardwired into (ostensibly) portable codebases.

UnsafeAtomic.Storage has the additional benefit that we can attach API to it (init(_:), dispose()) that would be inappropriate on _AtomicStorage, esp. in the case where atomic types use Self as their atomic storage.

@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
@frozen
public struct UnsafeAtomicLazyReference<Instance: AnyObject> {
public typealias Value = Instance?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be clearer to remove this typealias, and use Instance? directly.

(Instance and Instance? are already used in the store/load methods.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Value is part of the new unsafe API pattern for high-performance synchronization primitives. This is pure 100% ceremony, but it helps establishing / highlighting patterns for such types.

stdlib/public/Atomics/UnsafeAtomic.swift.gyb Outdated Show resolved Hide resolved
@lorentey
Copy link
Member Author

@swift-ci test

@swift-ci

This comment has been minimized.

@swift-ci

This comment has been minimized.

@lorentey
Copy link
Member Author

@swift-ci test

@swift-ci

This comment has been minimized.

@swift-ci

This comment has been minimized.

@shahmishal
Copy link
Member

Please update the base branch to main by Oct 5th otherwise the pull request will be closed automatically.

  • How to change the base branch: (Link)
  • More detail about the branch update: (Link)

@lorentey
Copy link
Member Author

lorentey commented Oct 2, 2020

Closing -- Atomics are published as a standalone package for now: https://github.com/apple/swift-atomics

@lorentey lorentey closed this Oct 2, 2020
@AnthonyLatsis AnthonyLatsis removed the swift evolution pending discussion Flag → feature: A feature that has a Swift evolution proposal currently in review label Oct 25, 2023
@AnthonyLatsis AnthonyLatsis linked an issue Oct 25, 2023 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[SR-9144] Low-level atomics in Swift