Skip to content
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

remove @fence #21585

Merged
merged 3 commits into from
Oct 4, 2024
Merged

remove @fence #21585

merged 3 commits into from
Oct 4, 2024

Conversation

Rexicon226
Copy link
Contributor

@Rexicon226 Rexicon226 commented Oct 3, 2024

closes #11650

Release Notes:

In Zig 0.14, @fence has been removed. @fence was provided to be consistent with the C11 memory model, however, it complicates semantics by modifying the memory orderings of all previous and future atomic operations. This creates unforeseen constraints that are hard to model in a sanitizer. Fences can be substituted by either upgrading atomic memory orderings or adding new atomic operations.

The most common use cases for @fence can be replaced by utilizing stronger memory orderings or by introducing a new atomic variable.

StoreLoad Barriers

The most common use case is @fence(.seq_cst). This is primarily used to ensure a consistent order between multiple operations on different atomic variables.
For example:

thread-1:                     thread-2:
    store X         // A          store Y          // C
    fence(seq_cst)  // F1         fence(seq_cst)   // F2   
    load  Y         // B          load  X          // D

The goal is to ensure either load X (D) sees store X (A), or load Y (B) sees store Y (C). The pair of Sequentially Consistent fences guarantees this via two invariance.

Now that @fence is removed, there are other ways of achieving this relationship:

  • Making all related stores and loads (A, B, C, and D) SeqCst, including them all in the total order.
  • Making a store (A/C) Acquire and its matching load (D/B) Release. Semantically, this would mean upgrading them to read-modify-write operations, which could be such ordering. Loads can be replaced with a non-mutating RMW, i.e. fetchAdd(0) or fetchOr(0).
    Optimizers like LLVM may reduce this into a @fence(.seq_cst) + load internally.

Conditional Barriers

Another use case for fences is conditionally creating a synchronizes-with relationship with previous or future atomic operations, using Acquire or Release respectively. A simple example of this in the real world is an atomic reference counter:

fn inc(counter: *RefCounter) void {
    _ = counter.rc.fetchAdd(1, .monotonic);
}

fn dec(counter: *RefCounter) void {
    if (counter.rc.fetchSub(1, .release) == 1) {
        @fence(.acquire); 
        counter.deinit(); 
    }
}

The load in the fetchSub(1) only needs to be Acquire for the last ref-count decrement to ensure previous decrements
happen-before the deinit(). The @fence(.acquire) here creates this relationship using the load part of the fetchSub(1).

Without @fence, there are two approaches here:

  1. Unconditionally strengthen the desired atomic operations with the fence's ordering.
    For example:
    if (counter.rc.fetchSub(1, .acq_rel) == 1) {
  1. Conditionally duplicate the desired store or load with the fence's ordering.
    For example:
    if (counter.rc.fetchSub(1, .release) == 1) {
        _ = counter.rc.load(.acquire);

The Acquire will synchronize-with the longest release-sequence in rc's modification order, making all previous decrements happen-before the deinit().

Synchronize External Operations

The least common usage of @fence is providing additional synchronization to atomic operations the programmer has no control over (i.e. external function calls). Using a @fence in this situation relies on the "hidden" functions having atomic operations with undesirably weak orderings.

Ideally, the "hidden" functions would be accessible to the user and they could simply increase the order in the source code. But if this isn't possible, a last resort is introducing an atomic variable to simulate the fence's barriers. For example:

thread-1:                    thread-2:
   queue.push()                e = signal.listen()
   fence(.seq_cst)             fence(.seq_cst)
   signal.notify()             if queue.empty(): e.wait()
thread-1:                    thread-2:
   queue.push()                e = signal.listen()
   fetchAdd(0, .seq_cst)       fetchAdd(0, .seq_cst)
   signal.notify()             if queue.empty(): e.wait()

@Rexicon226 Rexicon226 requested a review from kprotty as a code owner October 3, 2024 23:12
@kprotty kprotty added breaking Implementing this issue could cause existing code to no longer compile or have different behavior. standard library This issue involves writing Zig code for the standard library. release notes This PR should be mentioned in the release notes. labels Oct 4, 2024
@The-King-of-Toasters
Copy link
Contributor

I believe the zig_fence definitions in the *.h files should also be removed, no?

@Rexicon226
Copy link
Contributor Author

I believe the zig_fence definitions in the *.h files should also be removed, no?

Good catch, I totally missed that!

@@ -4218,11 +4218,10 @@ pub fn print(self: *Writer, arg0: []const u8, arg1: i32) !void {
{#header_close#}

{#header_open|Atomics#}
<p>TODO: @fence()</p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good thing I never bothered to write these docs 🙃

@andrewrk andrewrk enabled auto-merge (squash) October 4, 2024 19:50
@andrewrk andrewrk merged commit 043b1ad into ziglang:master Oct 4, 2024
10 checks passed
@Rexicon226 Rexicon226 deleted the remove-fence branch October 4, 2024 22:21
Ultra-Code pushed a commit to Ultra-Code/zig that referenced this pull request Oct 8, 2024
richerfu pushed a commit to richerfu/zig that referenced this pull request Oct 28, 2024
leroycep added a commit to leroycep/libxev that referenced this pull request Jan 26, 2025
Relevant changes

- [remove @Fence()][#21585]
- `tv_` prefix removed from `linux.timespec` in [std.c reorganization][2]
- `linux.timerfd_create` now accepts clockid_t enum in [std.c reorganization][3]
- `linux.clockid_t` is now a non-exhaustive enum in [std.c reorganization][4]

[#21585]: ziglang/zig#21585
[2]: ziglang/zig@e8c4e79#diff-37593602824d84b26d4345c606faf7be68c398f3bd74016afdaa6ba5dc6acaafL6224-L6233
[3]: ziglang/zig@e8c4e79#diff-37593602824d84b26d4345c606faf7be68c398f3bd74016afdaa6ba5dc6acaafL1965-R1974
[4]: ziglang/zig@e8c4e79#diff-37593602824d84b26d4345c606faf7be68c398f3bd74016afdaa6ba5dc6acaafL4032-R4056
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking Implementing this issue could cause existing code to no longer compile or have different behavior. release notes This PR should be mentioned in the release notes. standard library This issue involves writing Zig code for the standard library.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

remove @fence from the language
4 participants