Skip to content

Commit

Permalink
Add atomics support in the standard library
Browse files Browse the repository at this point in the history
  • Loading branch information
lucteo committed Dec 20, 2024
1 parent 571967b commit c5720a9
Show file tree
Hide file tree
Showing 9 changed files with 3,694 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@
**.lib
**.7z
llvm.pc
Tools/__pycache__
122 changes: 122 additions & 0 deletions StandardLibrary/Sources/LowLevel/Threading/Atomic.hylo
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 StandardLibrary/Sources/LowLevel/Threading/AtomicOrderings.hylo
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/// Specifies the memory ordering sematics for an atomic load operation.

Check warning on line 1 in StandardLibrary/Sources/LowLevel/Threading/AtomicOrderings.hylo

View workflow job for this annotation

GitHub Actions / Spell Check using typos

"sematics" should be "semantics".
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()
}
Loading

0 comments on commit c5720a9

Please sign in to comment.