I'll start off by saying that I haven't read all the replies, sorry!
I think interop with C and C++ atomics make sense. However, what this actually means is "participate in a memory model", that is: you have X threads, making accesses to Y memory locations, and there's a model which details the possible outcomes for these accesses and threads. It doesn't really matter what the thread languages are! Just that they access the same memory locations.
I'm therefore saying that you want a memory model, compatible with C and C++'s.
In other words, you want to have a model which, given threads and accesses, outputs the possible valid values observed by the program. You can then list interesting litmus tests, and run them a few billion times on relevant hardware to make sure your compiler lowering and the hardware actually obey the memory model. Concretely, here's an example of such an interactive memory model checker for C++.
There's plenty of research in that area.
- A good starting point is Mark Batty's work, since he authored the C / C++ memory models for C++11.
- In particular https://dl.acm.org/doi/abs/10.1145/1925844.1926394.
- Jade Alglave has also done great work in this field.
- This leads you to Peter Sewell's work.
- You probably want to look at what's been done for JavaScript and WebAssembly, for example this paper.
- Work is still ongoing there, e.g. to repair certain aspects.
- Or by NVIDIA.
Other interesting reads related to C++ directly:
- Tearable accesses are something that might be useful.
- Linux's approach models atomicity in code, not in memory locations.
- Deferred reclamation is something you ought to consider as well as hazard pointer and RCU.
FWIW, the WebAssembly and JS memory models handle mixing seq_cst with non-atomic accesses (because the instructions are atomic, not the locations). Also, you might want to think what unaligned / mismatched sizes do, if you allow it at all (C and C++ don't because atomicity is a property of the type, not the code, so a location can't be mixed atomic / not, nor different sizes).
IMO you should force lock-free for everything. We don't even ship libatomic on our platforms so it's gotta be lock free, or fail linking. I wouldn't bother with interop with non-lock-free atomics in C and C++.
One thing you ought to consider is only allowing specific ordering for some atomic objects. Like “you can only do acquire release on this”. Or “this object is sequentially consistent only”. Instead of allowing arbitrary orderings. It greatly simplifies things. Doesn’t hurt interop really. Rarely if ever loses capabilities.
If you do a full memory model then you probably still want to detail what happens when individual locations are modified inconsistently, but Swift itself should only allow one location to be one of:
- relaxed
- consume / release
- acquire / release
- seq_cst
- non-atomic
You also want to consider volatile atomic (separate from volatile, and separate from atomic). C and C++ have volatile atomic, and if you want interop you probably want this as well. Or is this implicit in Swift, i.e. can a compiler optimize around atomic accesses by assuming that the program is data-race free? Basically, in C++ a data race is UB, so LLVM can (and does) optimize assuming that there isn't a data race. See N4455 No Sane Compiler Would Optimize Atomics
You also need to think about notify: P1135R6: The C++20 Synchronization Library
You'll want to think about out-of-thin-air values, and pray that someone fixes the issues there... Similarly, C and C++ have a pointer zap issue.
On consume: yes p0750r1: Consume is still the state of the art. Until someone implements a proof of concept in a compiler (i.e. tracks dependencies for real, preventing things like GVN from breaking them), then the committee will ask that people avoid consume, and compilers will auto-upgrade consume to acquire. I don't think it's particularly, hard, we just need to track that dependency through IR and teach various parts of LLVM to leave dependencies alone instead of ICE'ing.
One last thing: you probably want to ignore C++ atomic flag, or at least match what C++20 has for it? It's kind of a historical thing, we almost removed it, but then decided to fix it instead.