Skip to content

Add Volatile ReadBarrier and WriteBarrier - #597

Merged
SimonCropp merged 1 commit into
mainfrom
volatile-barriers
Sep 10, 2026
Merged

Add Volatile ReadBarrier and WriteBarrier#597
SimonCropp merged 1 commit into
mainfrom
volatile-barriers

Conversation

@SimonCropp

Copy link
Copy Markdown
Owner

Two members. The audit item said net9; they are actually net10 — absent from the 9.0.20 ref pack, present in 10.0.12 — so the guard is !NET10_0_OR_GREATER and net9.0 gets them too.

A full fence is the only option, and it is not free

Volatile.ReadBarrier is an acquire barrier (LoadLoad + LoadStore) and Volatile.WriteBarrier is a release barrier (StoreStore + LoadStore). Below net10 there is no one-way barrier in the BCL at all — Interlocked.MemoryBarrier and Thread.MemoryBarrier are both full fences, and both are available everywhere Polyfill targets, including netstandard2.0 and net461.

A full fence provides a superset of the orderings each one-way barrier guarantees, so the polyfill is correct, never weaker. The divergence is cost, and it is worth being precise about because it is not the usual "slightly slower" case: on x86 and x64 the BCL's ReadBarrier/WriteBarrier constrain JIT reordering without emitting any processor instruction, since the hardware memory model already provides acquire/release for ordinary loads and stores. A full fence emits a real serialising instruction there. So the polyfilled versions cost something the BCL versions cost nothing for, rather than merely costing more. That is //Note:d on both.

What the tests do and do not prove

Memory ordering is not deterministically testable from a unit test. The canonical message-passing pattern — payload = i; WriteBarrier(); publish(i) against wait(i); ReadBarrier(); read payload — passes on x86 and x64 even with no barrier at all, so a green test says nothing about the ordering guarantee.

Rather than dress that up, the tests cover what they actually can: that both calls compile and complete on every target framework, and that the barriers behave under real two-thread concurrency across 200 publish/consume iterations with a bounded spin. A comment at the top of the file states plainly what is not being proven. This would catch a barrier that throws, that fails to resolve on some target, or that deadlocks — not a barrier that is too weak.

Result

API count 1109 → 1111.

Solution clean in Release, Consume clean across all 22 TFMs, tests green on net11.0 (1699), net10.0 (1699), net9.0 (1699), net8.0 (1696), net462 (1661), plus PublicTests, EmbeddedTests, UnsafeTests, NoRefsTests and NoExtrasTests.

These are net10 rather than net9, so the guard covers everything below net10.

Both are implemented as Interlocked.MemoryBarrier, a full fence. That is strictly
stronger than the acquire-only and release-only barriers the BCL emits, so it is
correct, but it is not free: on x86 and x64 the BCL versions constrain the JIT
without emitting a processor instruction, where a full fence does. Noted on both.

Memory ordering cannot be asserted deterministically from a unit test, since the
message passing pattern used to exercise them succeeds on x86 and x64 even with no
barrier at all. The tests cover that the calls compile and complete on every target
and behave under real concurrency, and the comment says what they do not prove.

API count 1109 -> 1111.
@SimonCropp
SimonCropp merged commit 3daf938 into main Sep 10, 2026
3 of 5 checks passed
@SimonCropp
SimonCropp deleted the volatile-barriers branch September 10, 2026 05:55
@SimonCropp SimonCropp added this to the 11.3.0 milestone Sep 10, 2026
This was referenced Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant