SE-0282: Low-Level Atomic Operations

I see. Here's the code in question:

// warning: invalid code! do not c&p
class AtomicCounter {
  private var _value = 0

  init() {
    UnsafeAtomic<Int>.initialize(address: &_value, to: 0)
  }

  deinit {
    _value.destroy(address: &_value)
  }

  func increment() {
    UnsafeAtomic<Int>.wrappingIncrement(address: &_value, by: 1, ordering: .relaxed)
  }

  func get() -> Int {
    UnsafeAtomic<Int>.load(address: &_value, ordering: .relaxed)
  }
}

The fundamental language problem, independent of this proposal, is that it isn't well-defined to take the address of an ivar as a free-floating pointer, because ivars are expected to be governed by the exclusivity model and only be read or written by well-behaved formal accesses. &_value is an inout access, even when it undergoes a pointer conversion, and so exerts exclusivity on the storage, so it can't be used for atomic storage where you intentionally want to accommodate simultaneous accesses. The fact that the UnsafeAtomic types don't work with the & sugar is a feature, not a bug. If you want to avoid extra indirection in a class instance in the language as it exists today, you would have to use ManagedBuffer's tail allocation to store your atomic variables; you could then provide an UnsafeAtomic property that converts the pointer to the ManagedBuffer's tail allocation. Static vs. instance methods for UnsafeAtomic's interface therefore do not fundamentally improve the potential efficiency of code using atomics today, because atomics must live in non-property raw memory.

Providing freely-addressable raw storage inside instances is a separate problem we need to solve. Once we have that, then you would be able to form an UnsafeAtomic from a pointer to a raw storage ivar and avoid indirection that way:

class AtomicCounter {
  // Strawman syntax for a "raw" variable with stable pointers
  @raw private var _counterStorage: Int = 0

  private var counter: UnsafeAtomic<Int> {
    // Strawman raw properties would not be subject to our usual constraints
    // about taking their address
    return UnsafeAtomic(&_counterStorage)
  }

  func increment() { counter.wrappingIncrement(by: 1, ordering: .relaxed) }

  func get() -> Int { return counter.load(ordering: .relaxed) }
}

In previous discussions about this, it seemed promising that a property wrapper could be used to abstract away the underlying raw storage so you only have to declare the surface property of UnsafeAtomic type.

3 Likes