SE-0282 (Review #2): Interoperability with the C Atomic Operations Library

SE-0176 is a good starting point.

1 Like

"Aren't allowed" here means that breaking the rules leads to undefined behavior. Unfortunately, it isn't practical to guarantee a clean runtime trap (much less a compile-time error) for all cases of invalid concurrent access. (The Thread Sanitizer is hugely helpful in detecting such problems, but leaving it enabled in production wouldn't be a viable choice for many Swift applications.)

Like Cory says, SE-0176 introduced the Law of Exclusivity and implemented guaranteed diagnosis of violations in some (but not all) single-threaded cases. (Which moved these cases from the realm of undefined behavior to compiler errors and/or runtime traps.) This proposal changed the language such that some (ostensibly) working code ceased to compile/run in the new Swift release that included its implementation.

As it happens, SE-0176 technically also disallowed atomic concurrent access to the same variable -- not because of any actual desire to outlaw these, but because the concept of an atomic access does not exist in the language. This leaves it unclear how/if atomic operations imported from C would successfully interoperate with Swift constructs. This more focused second version of the proposal concentrates on clarifying this point.

Of course, atomic operations and memory orderings have always worked in Swift -- dispatch queues, locks and similar synchronization constructs deeply rely on them, and such constructs are used in essentially all non-trivial Swift code. But I think it's worth spending a proposal on putting them on a little more stable foundation. (Note that SE-0282 falls far short of specifying a concurrency memory model for Swift -- but by simply declaring that Swift adopts a C++-style model that interoperates with C's atomic library, and highlighting some unique aspects of this interaction, I think we can already go a long way towards enabling people to start using low-level atomic primitives directly from Swift code.)

6 Likes

Indeed. My comment was that the proposed amendment to the law:

Two accesses to the same variable aren't allowed to overlap unless both accesses are reads or both accesses are atomic .

Might be read as saying that overlapping atomic/non-atomic reads would be allowed.

Of course, while we’re importing C’s operations, we can’t add definition to things they leave undefined - but out of interest, is there some practical reason they couldn’t be allowed when using Swift-native atomic operations? Something to do with CPU caching perhaps?

1 Like

Karoy, thank you very much for continuing to work on atomics!

Sorry, but I can't find the community discussion of the second revision -- could someone point me at the thread?

I'm afraid I can't provide a review of this proposal because I have a hard time understanding what exactly is being proposed, what changes are made to the language, what changes are made to the library, and what is just sample code that demonstrates usage of newly added capabilities.

I don't understand the role of the https://github.com/apple/swift-se-0282-experimental package. Is it intended to be a just some experimental code, or is it intended to become a part of the standard library? I get mixed signals:

  • On one hand, the proposal says "Effect on ABI Stability: None." Therefore, no code will be added to the standard library.

  • On the other hand, the location and name of this package makes it look like a Standard Library Preview package, as described by https://swift.org/blog/preview-package/. That page says

    The preview package provides access to functionality that has been accepted into the Swift standard library through the Swift Evolution process, but has not yet shipped as part of an official Swift release.

    I interpret that to mean that the preview package will be added to the standard library after the review concludes.

The proposal also says "While this proposal enables the use of C's atomics operations in Swift code", but I don't understand through which mechanism. In the preview package (which might not even become a part of the standard library), the entry points for wrappers of C's stdatomic.h APIs are a part of the _AtomicShims module. The underscore in the name implies that _AtomicShims is an implementation detail, not a public API. If so, I don't understand how the proposal enables the use of C's atomics from Swift.

I might be completely misunderstanding the direction and the intent, but it seems to me that the proposal that enables the use of C's atomics in Swift would be changing the Clang importer to enable importing _Atomic qualified types as some corresponding Swift overlay types, with corresponding wrappers from _AtomicShims available from the libc overlay.


If the proposal only clarifies the interaction between Swift's and C's memory model, does not change the standard library API, does not change Clang importer behavior, does not add any new modules to the standard library, does not change the libc overlay etc., then I support the proposal. However, the https://github.com/apple/swift-se-0282-experimental package must be renamed so that it does not look like a Standard Library Preview package that contains stable APIs that were approved by the evolution process. It would be also great if the proposal text was also adjusted to clearly say that the referenced repository is just sample code and not a part of the proposal itself.

One thing that the proposal (and the provided preview package) does not address is the consume memory ordering. Could we say that we upgrade it to acquire from the point of view of Swift?

Hi @gribozavr, my understanding is that this is a clarification and endorsement of a memory model in Swift, without adding any language or library features. The linked package is just an "FYI".

3 Likes

Ah, interesting. For the purposes of exclusivity, atomic accesses are distinct from either read or write accesses.

I believe the practical problem is with compiler optimizations (and instruction reordering). I found Hans Boehm's classic 2011 article a good read on this.

To be clear, this proposal does not expose any Swift-native atomic operations, so such things aren't really on the table yet. Later on, as we hopefully start formalizing Swift's concept of memory, I do think there will be plenty of opportunity to define things that C/C++ leaves undefined. But the need for continued interoperability between these languages means that we ought to be careful not to let them diverge too much. C++ is not standing still -- extensions such as tearing atomics are being discussed, and the memory model is being refined with every new version of the standard. I think it would be best if we kept things in sync as much as possible.

Which is not to say Swift cannot do things its own way or go a little bit ahead of where C++ is. The APIs provided in the initial version of this proposal (now in the supplementary package) already made some (inconsequential) departures from C++17: atomic operations are expected to have lock-free implementations, and CAS already supports all combinations of success/failure orderings. (Although internally it does currently "round them up" to the nearest C-supported combos, for obvious reasons.)

I expect we would eventually provide limited support for mixed atomic/nonatomic access, probably through something like explicit nonatomicLoad operations. (I don't know if this would happen before or after C++ takes a similar step.) Allowing/encouraging regular everyday reads of "atomic" memory locations feels like it would be a mistake, though -- which is one reason why I am so hesitant about directly exposing pointer-based atomics.

Why exactly do you think mixed atomic-nonatomic access is worth bringing up at this point?

Wouldn't it be better to disallow memory_order_consume until it's fixed?

I'm sorry to hear that. To clarify, the section titled "Proposed Solution" contains the proposed changes. In a nutshell, it boils down to

  1. Stating that Swift's memory model is compatible with that of C,
  2. Amending the Law of Exclusivity as introduced in SE-0176 to make sure we don't actually outlaw concurrent atomic access.

The name merely indicates that the package is associated with this proposal. ¯\_(ツ)_/¯

The preview package provides access to new functionality that has been accepted into the standard library through the Swift Evolution process. This proposal doesn't add any new stdlib API, so it is unrelated to the preview package.

It would probably be a good idea to rename the package in case the proposal is accepted, so that people will have an easier time using it. The README explicitly warns potential users that this may happen.

It does this by resolving an uncomfortable ambiguity introduced by a strict reading of SE-0176.

It has always been possible to call C's atomic operations in Swift code, by wrapping them in importable C constructs of varying complexity. We don't need to do anything particular to enable this further.

The problem as I see it here is that I don't see what Swift construct we would map imported _Atomic types. Move-only types with directly addressable components could probably help with that, too.

Upgrading to acquire seems to be a pragmatic choice. (Although -- can't llvm track data dependencies through language boundaries? :thinking:)

3 Likes

How/why would we disallow that? The Swift runtime is itself using consume ordering, and AIUI it does work in Swift the same way it does in C (as implemented by Clang) -- which is to say it behaves like acquire ordering.

memory_order_consume tried to solve a real problem; unfortunately as I understand it failed at that, but artificially forbidding it would just add interoperability problems without resolving any actual issue.

Given consume's problems, the initial version of this proposal (and now the example package) do not expose a consuming ordering. If someone wants to create an atomics package that does include consume, then so be it -- it will be largely pointless, but I don't see how we could possibly disallow that.

It can't be formally disallowed, but we can explicitly recommend against it until there is a meaningful implementation in clang/llvm.

As I see it, the existence of the "upgrade" is a problem, in that someone can write an algorithm that they think uses consume ordering, and it cannot be tested because the compiler silently upgrades it to "acquire". More than likely, when actual support arrives, the implementation will stop working. The upgrade is a nice solution from the point of view of the compiler, but it's a bad solution from the point of view of users.

As I understand it, the consume ordering will never get implemented in clang (or any other C/C++ compiler). The problem is that consume as currently defined is not implementable. The fix will most likely involve replacing consume with new APIs rather than merely tweaking its semantics.

It is always safe to make an operation “more atomic”, or to give it stricter ordering guarantees than it had. Acquire has all of the guarantees of consume, and more.

Basically, there is no observable side-effect.

Isn't that a good justification for explicitly discouraging its use? If it indeed never gets implemented, at best it creates duplicate code paths in libraries. If it does get implemented, a user code might start behaving differently. There's no upside.

That's the view from the compiler implementor. The view from an algorithm tester is you can't test what's silently upgraded. You can write an algorithm that requires acquire ordering and annotate it (wrongly) with consume ordering. Nothing will fail, because the compiler lies.

The question still stands.

A huge +1 to that. I completely support officially recognizing these points, which describe the understanding we had implicitly all along. Thank you!

For me, the issue is that the name looks like https://github.com/apple/swift-se0270-range-set, which implements APIs that will appear in the standard library. A user who does not carefully read the proposal can't tell the difference (I couldn't tell the difference even after reading the proposal). Furthermore, https://github.com/apple/swift-se-0282-experimental says "We expect the package will stabilize if/when SE-0282 is accepted." which suggests that it will contain a stable API.

I'm sorry for the confusion.

Thinking more about it, if we don't want "consume" in Swift, we should specify two things -- how Swift code sees "consume" operations executed by C code (suggestion: the same way as relaxed), and what Swift code should do if it wants to execute a consume operation (suggestion: use acquire).

There wasn’t any per se, but it more or less falls out of the first review thread: people agreed with the memory model, but didn’t agree about the api. This keeps the memory model and jettisons the api.

Right. The core team felt it was appropriate for this subset of the original proposal to go straight back to review without a need for a further pitch phase.

1 Like

I'm strongly opposed to ever adding "consume" ordering to a Swift memory memory model. I'm also strongly opposed to "upgrading" C's consume ordering to aquire". This would convert code that intentionally uses the most efficient ordering possible in that circumstance into code that uses the least efficient ordering.

Rather, we should ensure that LLVM continues to respect data dependencies for all atomic orderings.

The existence of a "consume" ordering is a tragic mistake because it introduces widespread undefined behavior in code that naturally relies on data dependencies, but yields no benefit. Swift should take the opportunity to define away that undefined behavior.

I admit, I don't understand why LLVM's langref currently recommends implementing memory_order_consume using an Acquire barrier. It should probably be Unordered, and data dependencies on Unordered access should be respected by LLVM optimization.

4 Likes

I'd guess that is the reason why LLVM does not implement "consume" in the way you suggested (even though it is a very reasonable suggestion!) This new constraint about preserving data dependencies would be a fundamental new invariant for LLVM transformations. Ensuring that we uphold it everywhere would require auditing and fixing a lot of code.

1 Like

I understand the decision, but for the purposes of transparency to people who are not on the core team and not employed by Apple, I'd prefer that the core team did not take such shortcuts in future.

3 Likes
Terms of Service

Privacy Policy

Cookie Policy