-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add atomics support in the standard library
- Loading branch information
Showing
9 changed files
with
3,694 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,3 +22,4 @@ | |
**.lib | ||
**.7z | ||
llvm.pc | ||
Tools/__pycache__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
|
||
/// Defines atomic operations for `T`. | ||
public type Atomic<T: AtomicRepresentable>: SemiRegular { | ||
|
||
/// The storage for the atomic operations. | ||
var storage: T.AtomicRepresentation | ||
|
||
/// Initializes `self` with the zero value. | ||
public init() { | ||
&storage = .new() | ||
} | ||
|
||
/// Initializes `self` with `value`. | ||
public init(value: sink T) { | ||
&storage = .new() | ||
store(value, ordering: /*AtomicStoreOrdering.relaxed*/ .new(value: 0)) | ||
// TODO: using atomic ordering constants produce linker errors | ||
} | ||
|
||
/// Atomically load the value from the atomic, using `ordering`. | ||
public fun load(ordering: AtomicLoadOrdering) -> T { | ||
return T.decodeAtomicRepresentation(storage.load(ordering: ordering)) | ||
} | ||
|
||
/// Atomically store `value` into the atomic, using `ordering`. | ||
public fun store(_ value: sink T, ordering: AtomicStoreOrdering) inout { | ||
storage.store(T.encodeAtomicRepresentation(value), ordering: ordering) | ||
} | ||
|
||
/// Atomically exchanges the value in the atomic with `desired`, using `ordering`. | ||
public fun exchange(desired: sink T, ordering: AtomicUpdateOrdering) inout -> T { | ||
return T.decodeAtomicRepresentation(storage.exchange(desired: T.encodeAtomicRepresentation(desired), ordering: ordering)) | ||
} | ||
|
||
/// Atomically exchanges the atomic with `desired`, if the original value is equal to `expected`, using `ordering`. | ||
/// Returns a tuple containing the original value and a boolean indicating whether the exchange was successful. | ||
public fun compare_and_exchange(expected: sink T, desired: sink T, ordering: AtomicUpdateOrdering) inout -> {exchanged: Bool, original: T} { | ||
let r = storage.compare_and_exchange( | ||
expected: T.encodeAtomicRepresentation(expected), | ||
desired: T.encodeAtomicRepresentation(desired), | ||
success_ordering: ordering, | ||
failure_ordering: failure_ordering(for: ordering)) | ||
return (exchanged: r.0, original: T.decodeAtomicRepresentation(r.1)) | ||
} | ||
|
||
/// Atomically exchanges the atomic with `desired`, if the original value is equal to `expected`, using `success_ordering` for the update, and `failure_ordering` for loading the new value in case of failure. | ||
/// Returns a tuple containing the original value and a boolean indicating whether the exchange was successful. | ||
public fun compare_and_exchange(expected: sink T, desired: sink T, success_ordering: AtomicUpdateOrdering, failure_ordering: AtomicLoadOrdering) inout -> {exchanged: Bool, original: T} { | ||
let r = storage.compare_and_exchange( | ||
expected: T.encodeAtomicRepresentation(expected), | ||
desired: T.encodeAtomicRepresentation(desired), | ||
success_ordering: success_ordering, | ||
failure_ordering: failure_ordering) | ||
return (exchanged: r.0, original: T.decodeAtomicRepresentation(r.1)) | ||
} | ||
|
||
/// Same as `compare_and_exchange`, but may fail spuriously. | ||
public fun weak_compare_and_exchange(expected: sink T, desired: sink T, ordering: AtomicUpdateOrdering) inout -> {exchanged: Bool, original: T} { | ||
let r = storage.weak_compare_and_exchange( | ||
expected: T.encodeAtomicRepresentation(expected), | ||
desired: T.encodeAtomicRepresentation(desired), | ||
success_ordering: ordering, | ||
failure_ordering: failure_ordering(for: ordering)) | ||
return (exchanged: r.0, original: T.decodeAtomicRepresentation(r.1)) | ||
} | ||
|
||
/// Same as `compare_and_exchange`, but may fail spuriously. | ||
public fun weak_compare_and_exchange(expected: sink T, desired: sink T, success_ordering: AtomicUpdateOrdering, failure_ordering: AtomicLoadOrdering) inout -> {exchanged: Bool, original: T} { | ||
let r = storage.weak_compare_and_exchange( | ||
expected: T.encodeAtomicRepresentation(expected), | ||
desired: T.encodeAtomicRepresentation(desired), | ||
success_ordering: success_ordering, | ||
failure_ordering: failure_ordering) | ||
return (exchanged: r.0, original: T.decodeAtomicRepresentation(r.1)) | ||
} | ||
|
||
} | ||
|
||
/// Atomic operations for integer types | ||
public extension Atomic where T.AtomicRepresentation: IntegerPlatformAtomic, T: Numeric { | ||
|
||
/// Atomically updates `this` by adding `value` to it, using `ordering`, and return the original value. | ||
public fun fetch_add(_ value: sink T, ordering: AtomicUpdateOrdering) inout -> T { | ||
return T.decodeAtomicRepresentation(storage.fetch_add(T.encodeAtomicRepresentation(value), ordering: ordering)) | ||
} | ||
|
||
/// Atomically updates `this` by subtracting `value` from it, using `ordering`, and return the original value. | ||
public fun fetch_sub(_ value: sink T, ordering: AtomicUpdateOrdering) inout -> T { | ||
return T.decodeAtomicRepresentation(storage.fetch_sub(T.encodeAtomicRepresentation(value), ordering: ordering)) | ||
} | ||
|
||
/// Atomically updates `this` by using the maximum of `value` and itself, using `ordering`, and return the original value. | ||
public fun fetch_max(_ value: sink T, ordering: AtomicUpdateOrdering) inout -> T { | ||
return T.decodeAtomicRepresentation(storage.fetch_max(T.encodeAtomicRepresentation(value), ordering: ordering)) | ||
} | ||
|
||
/// Atomically updates `this` by using the minimum of `value` and itself, using `ordering`, and return the original value. | ||
public fun fetch_min(_ value: sink T, ordering: AtomicUpdateOrdering) inout -> T { | ||
return T.decodeAtomicRepresentation(storage.fetch_min(T.encodeAtomicRepresentation(value), ordering: ordering)) | ||
} | ||
|
||
/// Atomically updates `this` by performing a bitwise AND of `value` and itself, using `ordering`, and return the original value. | ||
public fun fetch_and(_ value: sink T, ordering: AtomicUpdateOrdering) inout -> T { | ||
return T.decodeAtomicRepresentation(storage.fetch_and(T.encodeAtomicRepresentation(value), ordering: ordering)) | ||
} | ||
|
||
/// Atomically updates `this` by performing a bitwise NAND of `value` and itself, using `ordering`, and return the original value. | ||
public fun fetch_nand(_ value: sink T, ordering: AtomicUpdateOrdering) inout -> T { | ||
return T.decodeAtomicRepresentation(storage.fetch_nand(T.encodeAtomicRepresentation(value), ordering: ordering)) | ||
} | ||
|
||
/// Atomically updates `this` by performing a bitwise OR of `value` and itself, using `ordering`, and return the original value. | ||
public fun fetch_or(_ value: sink T, ordering: AtomicUpdateOrdering) inout -> T { | ||
return T.decodeAtomicRepresentation(storage.fetch_or(T.encodeAtomicRepresentation(value), ordering: ordering)) | ||
} | ||
|
||
/// Atomically updates `this` by performing a bitwise XOR of `value` and itself, using `ordering`, and return the original value. | ||
public fun fetch_xor(_ value: sink T, ordering: AtomicUpdateOrdering) inout -> T { | ||
return T.decodeAtomicRepresentation(storage.fetch_xor(T.encodeAtomicRepresentation(value), ordering: ordering)) | ||
} | ||
|
||
} |
167 changes: 167 additions & 0 deletions
167
StandardLibrary/Sources/LowLevel/Threading/AtomicOrderings.hylo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
/// Specifies the memory ordering sematics for an atomic load operation. | ||
public type AtomicLoadOrdering: Regular { | ||
|
||
public memberwise init | ||
|
||
internal let value: Int | ||
|
||
} | ||
|
||
public extension AtomicLoadOrdering { | ||
|
||
/// Guarantees the atomicity of the load operation, but does not impose any ordering constraints | ||
/// on other memory operations. | ||
/// | ||
/// Corresponds to the `memory_order_relaxed` C++ memory ordering. | ||
public static let relaxed: AtomicLoadOrdering = .new(value: 0) | ||
|
||
/// An acquiring load operation syncrhonizes with a releasing store operation on the same atomic | ||
/// variable. The thread performing the acquiring operation agrees with the thread performing the | ||
/// releasing operation that all the subsequent load operations (atomic or not) on the acquiring | ||
/// thread happen after the atomic operation itself. | ||
/// | ||
/// Provides a barrier that prevents read/write memory operations to be reordered before the | ||
/// atomic load. This barrier can be used to acquire a lock. | ||
/// | ||
/// Corresponds to the `memory_order_acquire` C++ memory ordering. | ||
public static let acquiring: AtomicLoadOrdering = .new(value: 1) | ||
|
||
/// A sequentially consistent load operation provides the same guarantees as an acquiring load | ||
/// and also guarantees that all sequentially consistent operations on the atomic variable will | ||
/// have a total squential order, across all threads. | ||
/// | ||
/// Provides a barrier that prevents read/write memory operations to be reordered before and | ||
/// after the atomic load. | ||
/// | ||
/// Corresponds to the `memory_order_seq_cst` C++ memory ordering. | ||
public static let sequentially_consistent: AtomicLoadOrdering = .new(value: 4) | ||
|
||
} | ||
|
||
/// Specifies the memory ordering sematics for an atomic store operation. | ||
public type AtomicStoreOrdering: Regular { | ||
|
||
public memberwise init | ||
|
||
internal let value: Int | ||
|
||
} | ||
|
||
public extension AtomicStoreOrdering { | ||
|
||
/// Guarantees the atomicity of the store operation, but does not impose any ordering constraints | ||
/// on other memory operations. | ||
/// | ||
/// Corresponds to the `memory_order_relaxed` C++ memory ordering. | ||
public static let relaxed: AtomicStoreOrdering = .new(value: 0) | ||
|
||
/// A releasing store operation syncrhonizes with an acquiring load operation on the same atomic | ||
/// variable. The thread performing the releasing operation agrees with the thread performing the | ||
/// acquiring operation that all the previous store operations (atomic or not) on the releasing | ||
/// thread will be seen before the atomic operation itself. | ||
/// | ||
/// Provides a barrier that prevents read/write memory operations to be reordered after the | ||
/// atomic store. This barrier can be used to release a lock. | ||
/// | ||
/// Corresponds to the `memory_order_release` C++ memory ordering. | ||
public static let releasing: AtomicStoreOrdering = .new(value: 2) | ||
|
||
/// A sequentially consistent store operation provides the same guarantees as a releasing store | ||
/// and also guarantees that all sequentially consistent operations on the atomic variable will | ||
/// have a total squential order, across all threads. | ||
/// | ||
/// Provides a barrier that prevents read/write memory operations to be reordered before and | ||
/// after the atomic store. | ||
/// | ||
/// Corresponds to the `memory_order_seq_cst` C++ memory ordering. | ||
public static let sequentially_consistent: AtomicStoreOrdering = .new(value: 4) | ||
|
||
} | ||
|
||
/// Specifies the memory ordering sematics for an atomic read-modify-write (update) operation. | ||
public type AtomicUpdateOrdering: Regular { | ||
|
||
public memberwise init | ||
|
||
internal let value: Int | ||
|
||
} | ||
|
||
public extension AtomicUpdateOrdering { | ||
|
||
/// Guarantees the atomicity of the load operation, but does not impose any ordering constraints | ||
/// on other memory operations. | ||
/// | ||
/// Corresponds to the `memory_order_relaxed` C++ memory ordering. | ||
public static let relaxed: AtomicUpdateOrdering = .new(value: 0) | ||
|
||
/// An acquiring update operation syncrhonizes with a releasing store operation on the same | ||
/// atomic variable whose value it reads. The thread performing the acquiring operation agrees | ||
/// with the thread performing the releasing operation that all the subsequent load operations | ||
/// (atomic or not) on the acquiring thread happen after the atomic operation itself. | ||
/// | ||
/// Provides a barrier that prevents read/write memory operations to be reordered before the | ||
/// atomic operation. This barrier can be used to acquire a lock. | ||
/// | ||
/// Corresponds to the `memory_order_acquire` C++ memory ordering. | ||
public static let acquiring: AtomicUpdateOrdering = .new(value: 1) | ||
|
||
/// A releasing update operation syncrhonizes with an acquiring load operation on the same atomic | ||
/// variable that reads the updated value. The thread performing the releasing operation agrees | ||
/// with the thread performing the acquiring operation that all the previous store operations | ||
/// (atomic or not) on the releasing thread will be seen before the atomic operation itself. | ||
/// | ||
/// Provides a barrier that prevents read/write memory operations to be reordered after the | ||
/// atomic update. This barrier can be used to release a lock. | ||
/// | ||
/// Corresponds to the `memory_order_release` C++ memory ordering. | ||
public static let releasing: AtomicUpdateOrdering = .new(value: 2) | ||
|
||
/// An acquiring and releasing update operation syncrhonizes with both acquiring loads that read | ||
/// the updated value and with the releasing stores that update the value that this update reads. | ||
/// The thread performing the update operation agrees with the threads performing the acquiring | ||
/// load that all the previous store operations (atomic or not) on the releasing thread will be | ||
/// seen before the atomic operation itself. The thread performing the update operation also | ||
/// agrees with the threads performing the releasing store of the value read by the update that | ||
/// all the previous store operations (atomic or not) on the updating thread happen before the | ||
/// update. | ||
/// | ||
/// Provides a barrier that prevents read/write memory operations to be reordered before and | ||
/// after the atomic update. | ||
/// | ||
/// Corresponds to the `memory_order_seq_cst` C++ memory ordering. | ||
public static let acquiring_and_releasing: AtomicUpdateOrdering = .new(value: 3) | ||
|
||
/// A sequentially consistent update operation provides the same guarantees as an acquiring and | ||
/// releasing update and also guarantees that all sequentially consistent operations on the | ||
/// atomic variable will have a total squential order, across all threads. | ||
/// | ||
/// Provides a barrier that prevents read/write memory operations to be reordered before and | ||
/// after the atomic update. | ||
/// | ||
/// Corresponds to the `memory_order_seq_cst` C++ memory ordering. | ||
public static let sequentially_consistent: AtomicUpdateOrdering = .new(value: 4) | ||
|
||
} | ||
|
||
/// Transforms from an atomic update ordering to a corresponding atomic load ordering, to be used | ||
/// in `compare_and_exchange` operations to get the failure ordering from the success ordering. | ||
// TODO: this should be internal, but we have a bug that generates a linker error | ||
public fun failure_ordering(for ordering: AtomicUpdateOrdering) -> AtomicLoadOrdering { | ||
if ordering == /*AtomicUpdateOrdering.relaxed*/ AtomicUpdateOrdering(value: 0) { | ||
return /*AtomicLoadOrdering.relaxed.copy()*/ AtomicLoadOrdering(value: 0) | ||
} | ||
if ordering == /*AtomicUpdateOrdering.acquiring*/ AtomicUpdateOrdering(value: 1) { | ||
return /*AtomicLoadOrdering.acquiring.copy()*/ AtomicLoadOrdering(value: 1) | ||
} | ||
if ordering == /*AtomicUpdateOrdering.releasing*/ AtomicUpdateOrdering(value: 2) { | ||
return /*AtomicLoadOrdering.relaxed.copy()*/ AtomicLoadOrdering(value: 0) | ||
} | ||
if ordering == /*AtomicUpdateOrdering.acquiring_and_releasing*/ AtomicUpdateOrdering(value: 3) { | ||
return /*AtomicLoadOrdering.acquiring.copy()*/ AtomicLoadOrdering(value: 1) | ||
} | ||
if ordering == /*AtomicUpdateOrdering.sequentially_consistent*/ AtomicUpdateOrdering(value: 4) { | ||
return /*AtomicLoadOrdering.sequentially_consistent.copy()*/ AtomicLoadOrdering(value: 4) | ||
} | ||
trap() | ||
} |
Oops, something went wrong.