-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Adopt a Zig Memory Model #6396
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Note that in the language specification (#75) the memory model will be specified without referencing an external definition (even if it happens to match C11). |
I've also extensively tried to wrap my head around memory models, so I'll try to partake in the discussion when it eventually happens.
As long as clang supports C/C++11 - 20+, adopting their memory model puts us in the same boat, which seems reasonably stable to me (though maybe only on their target platforms). |
Andrew, have you ever read the way Pony language deals with concurrency? From what I have read, even though it is an actor-based oriented language using a GC, the type system has been formally verified, and the reference capabilities leave no option to write unsafe code. Maybe Zig could include something similar, it seems like a very elegant approach to safe concurrency. Some interesting documents worth reading: https://www.ponylang.io/media/papers/a_prinicipled_design_of_capabilities_in_pony.pdf https://uu.diva-portal.org/smash/get/diva2:1363822/FULLTEXT01.pdf |
Some stuff worth to follow: |
More relevant discussion on the c side of things: also @SpexGuy "The primary goal there is to ensure that ptr->int->ptr casts are preserved. Which is good, but there are many other operations that can cause provenance information to be lost. Loading a pointer from memory, for example, or obtaining one from an extern fn." Contrary positions: how iso c became unusable for OS devs holding the same position as Linus Torvalds and M. Anton Ertl: Ralf Jung explains that making stuff too platform specific does not reflect reality of what compilers do and gives an example why this is a bad idea: Further more Jung mentions that there is no data on what is possible in performance improvements with more UB, but that likely one does not use C in the beginning for that. |
That's an interesting discussion, but it might be better on a different issue. (So far this issue has largely only been about concurrent memory accesses.) Do we have an issue for discussing pointer provenance and aliasing rules? |
The context will remain the same, so it would be linked anyway.
If the thread becomes to long, we can make linked meta-issues. Only proposals to use them, but not how and why with semantics: #1108 #7742 There was also a related proposal, which sounds to me like it goes into direction of storing the provenance explicit by the programmer (though I have no idea what and how provenance is planned to be encoded and used): #7887 Related to pointer semantics is #15232. |
One formal description of ReleaseSafe could be inspirited by "A Formal Model of Checked C" by Li et al, although (bound-)"checked c" and the paper do no statements on allocation errors like double free. |
Another relevant idea on the Rust side is to put the provenance information as being part of the type akind to what CHERI is doing: https://gankra.github.io/blah/fix-rust-pointers/. This has been implemented and is described: https://gankra.github.io/blah/tower-of-weakenings/. |
An update with accurate description of PNVI-ae-udi and the Rust equivalent was Rust
C
LLVM
Example, where optimization will still fail with reason (not in Ralf's work), but described in comments
|
"Memory Consistency & Atomicity" consists of 2 parts, which have an effect on program correctness:
More details of memory barriers https://stackoverflow.com/a/61711095 // Software Memory Models in C++
// memory_order | fences
// relaxed | None
// consume | LoadLoad, LoadStore (fences only for vars that are data-dependent on that atomic)
// acquire | LoadLoad, LoadStore
// release | LoadStore, StoreStore
// acq_rel | LoadLoad, LoadStore, StoreStore
// seq_cst | All (default order) LoadStore fence means that reorderings from Load instruction
Store instruction into Store instruction
Load instruction are prevented for both compiler and CPU core. Default of all fences looks to be Not sure, if this is library is relevant: http://mintomic.github.io/. |
Current problems of C11 memory model (execution semantics for multiple CPU cores) outlined here: https://llvm.org/devmtg/2017-03/assets/slides/weak_memory_concurrency_in_c_cxx11_and_llvm.pdf
I think this is the related paper: https://people.mpi-sws.org/~viktor/papers/popl2015-c11comp.pdf Lots of work going on with different approaches, but desired optimizations could not be verified yet https://arxiv.org/pdf/2108.06944.pdf |
An idealized human readable model (currently restricted to sequential) memory access and incomplete for Rust is given in minirust. This is intended as the specification later to be verified against. A full specification is aimed for in https://github.com/ferrocene/specification, but this specification is like C and C++ wishful axiomatic ("style is axiomatic") and yet without any formal proof or reasoning. |
Suggested by Ralf, "Promising 2.0: Global Optimizations in Relaxed Memory Concurrency" by Lee et al. looks like the latest state of art on a model for weak memory semantics and summarizes the main problems of other models including the shortcomings of C11. Link https://dl.acm.org/doi/pdf/10.1145/3385412.3386010 TLDR;
|
Some notes from the excellent to read "Rust Atomics and Locks Low-Level Concurrency in Practice" by Mara Bos (https://marabos.nl/atomics/):
TODO Hardware protocols (MOESI etc), functionality, future and influence of hardware platform + configuration |
For example programs and a tool for checking, see "Exploring C Semantics and Pointer Provenance" Most notably, "concrete models, in which pointer values are simple machine words or integers" are used in HOL, seL4, "abstract models like that of CompCert in which pointers are represented as a pair of an abstract allocation identifier and an offset in the corresponding allocation block" and "abstract models can only support restricted forms of conversions between pointers and integer". More interestingly though, "PNVI-ae-udi tracks (1) ambiguities in provenance and (2) provenance exposure whereas VIP Appendum (20231012):
|
Some resources:
|
TL;DR: Zig should adopt an official memory model. Some options are to use the C/C++ memory model (easy), or its own better memory model (very hard).
(Note that this issue is about the semantics of concurrent access, not other just-as-important things like pointer aliasing rules and provenance. That should also be specified, but it's the job of another issue.)
The problem
The memory model determines which concurrent programs are valid, and is important as the ground truth for what transformations the compiler is allowed to do. Currently, the Zig memory model is by default "whatever LLVM says". It would be nice to have a Zig memory model independent of LLVM. LLVM memory model
This is by no means urgent -- despite being the subject of significant formal verification efforts, Rust still lacks its own memory model, instead defaulting to the C11/C++11 spec.
Here are some reasons to not just stick with LLVM's model:
The LLVM memory model also (somewhat necessarily) incorporates a lot of cruft, from C, Java, and probably more -- what could "starting over" with a memory model fix?
Reordering of dependent loads is allowed by C/C++
relaxed
loads, and maybe by Java non-volatiles (this is unclear to me, maybe someone else can say for sure). LLVM never allows reordering of dependent loads, by simply not supporting the DEC Alpha processor. If Zig is to ever be run on the DEC Alpha, Zig will have to take a stance on this.LLVM doesn't support C/C++
consume
operations, which are arguably broken. Instead, Clang changes allconsume
s toacquire
s. Consume semantics were introduced for a reason though, so it's worth thinking about how Zig could succeed where the C/C++ standard failed.LLVM has
unordered
for Java non-volatile, andmonotonic
for C/C++ relaxed. It also asserts thatmonotonic
is strictly more ordered thanunordered
. However, C/C++ relaxed allows some things that Java non-volatile doesn't. This section of the Java memory model has an example:Initially
x = 0
,y = 0
.int tmp1 = x;
int tmp2 = y;
if (tmp1 == 1) y = 1;
if (tmp2 == 1) x = 1;
Assuming relaxed loads/stores to
x
andy
, C/C++ allows this program to end withx = y = 1
, but Java non-volatiles disallow this result. This is largely considered a bug in the C/C++ spec, but (afaik) all fixes are either too restrictive or require significant changes to how the memory model is specified.Proposed solution
consume
. (This is what Rust has currently.)AtomicOrder.Relaxed
, which gets translated to LLVM's monotonic, and remove/deprecateAtomicOrder.Unordered
andAtomicOrder.Monotonic
.AtomicOrder
docs, including stating that Zig follows the C11/C++11 memory model.consume
is deemed useful in the future, introduce something likeconsume
in a way that fixes the poor C/C++ semantics. This could be implemented by LLVM withacquire
, and in a more fine-grained way in Stage2.The text was updated successfully, but these errors were encountered: