From 8ccdd5556f68a32a49219b67f34a6d31613c8306 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Thu, 10 Sep 2026 15:44:14 +1000 Subject: [PATCH] Add Volatile ReadBarrier and WriteBarrier 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. --- apiCount.include.md | 42 +++--- api_list.include.md | 8 ++ assemblySize.include.md | 76 +++++------ readme.md | 130 ++++++++++--------- src/Consume/Consume.cs | 6 + src/Polyfill/VolatilePolyfill.cs | 33 +++++ src/Split/net461/VolatilePolyfill.cs | 24 ++++ src/Split/net462/VolatilePolyfill.cs | 24 ++++ src/Split/net47/VolatilePolyfill.cs | 24 ++++ src/Split/net471/VolatilePolyfill.cs | 24 ++++ src/Split/net472/VolatilePolyfill.cs | 24 ++++ src/Split/net48/VolatilePolyfill.cs | 24 ++++ src/Split/net481/VolatilePolyfill.cs | 24 ++++ src/Split/net5.0/VolatilePolyfill.cs | 24 ++++ src/Split/net6.0/VolatilePolyfill.cs | 24 ++++ src/Split/net7.0/VolatilePolyfill.cs | 24 ++++ src/Split/net8.0/VolatilePolyfill.cs | 24 ++++ src/Split/net9.0/VolatilePolyfill.cs | 24 ++++ src/Split/netcoreapp2.0/VolatilePolyfill.cs | 24 ++++ src/Split/netcoreapp2.1/VolatilePolyfill.cs | 24 ++++ src/Split/netcoreapp2.2/VolatilePolyfill.cs | 24 ++++ src/Split/netcoreapp3.0/VolatilePolyfill.cs | 24 ++++ src/Split/netcoreapp3.1/VolatilePolyfill.cs | 24 ++++ src/Split/netstandard2.0/VolatilePolyfill.cs | 24 ++++ src/Split/netstandard2.1/VolatilePolyfill.cs | 24 ++++ src/Split/uap10.0/VolatilePolyfill.cs | 24 ++++ src/Tests/VolatilePolyfillTests.cs | 78 +++++++++++ 27 files changed, 733 insertions(+), 120 deletions(-) create mode 100644 src/Polyfill/VolatilePolyfill.cs create mode 100644 src/Split/net461/VolatilePolyfill.cs create mode 100644 src/Split/net462/VolatilePolyfill.cs create mode 100644 src/Split/net47/VolatilePolyfill.cs create mode 100644 src/Split/net471/VolatilePolyfill.cs create mode 100644 src/Split/net472/VolatilePolyfill.cs create mode 100644 src/Split/net48/VolatilePolyfill.cs create mode 100644 src/Split/net481/VolatilePolyfill.cs create mode 100644 src/Split/net5.0/VolatilePolyfill.cs create mode 100644 src/Split/net6.0/VolatilePolyfill.cs create mode 100644 src/Split/net7.0/VolatilePolyfill.cs create mode 100644 src/Split/net8.0/VolatilePolyfill.cs create mode 100644 src/Split/net9.0/VolatilePolyfill.cs create mode 100644 src/Split/netcoreapp2.0/VolatilePolyfill.cs create mode 100644 src/Split/netcoreapp2.1/VolatilePolyfill.cs create mode 100644 src/Split/netcoreapp2.2/VolatilePolyfill.cs create mode 100644 src/Split/netcoreapp3.0/VolatilePolyfill.cs create mode 100644 src/Split/netcoreapp3.1/VolatilePolyfill.cs create mode 100644 src/Split/netstandard2.0/VolatilePolyfill.cs create mode 100644 src/Split/netstandard2.1/VolatilePolyfill.cs create mode 100644 src/Split/uap10.0/VolatilePolyfill.cs create mode 100644 src/Tests/VolatilePolyfillTests.cs diff --git a/apiCount.include.md b/apiCount.include.md index b1c1c59d..331cc84c 100644 --- a/apiCount.include.md +++ b/apiCount.include.md @@ -1,28 +1,28 @@ -**API count: 1109** +**API count: 1111** ### Per Target Framework | Target | APIs | | -- | -- | -| `net461` | 1036 | -| `net462` | 1036 | -| `net47` | 1035 | -| `net471` | 1034 | -| `net472` | 1030 | -| `net48` | 1030 | -| `net481` | 1030 | -| `netstandard2.0` | 1032 | -| `netstandard2.1` | 885 | -| `netcoreapp2.0` | 955 | -| `netcoreapp2.1` | 896 | -| `netcoreapp2.2` | 896 | -| `netcoreapp3.0` | 857 | -| `netcoreapp3.1` | 856 | -| `net5.0` | 726 | -| `net6.0` | 627 | -| `net7.0` | 478 | -| `net8.0` | 357 | -| `net9.0` | 236 | +| `net461` | 1038 | +| `net462` | 1038 | +| `net47` | 1037 | +| `net471` | 1036 | +| `net472` | 1032 | +| `net48` | 1032 | +| `net481` | 1032 | +| `netstandard2.0` | 1034 | +| `netstandard2.1` | 887 | +| `netcoreapp2.0` | 957 | +| `netcoreapp2.1` | 898 | +| `netcoreapp2.2` | 898 | +| `netcoreapp3.0` | 859 | +| `netcoreapp3.1` | 858 | +| `net5.0` | 728 | +| `net6.0` | 629 | +| `net7.0` | 480 | +| `net8.0` | 359 | +| `net9.0` | 238 | | `net10.0` | 182 | | `net11.0` | 58 | -| `uap10.0` | 1022 | +| `uap10.0` | 1024 | diff --git a/api_list.include.md b/api_list.include.md index 668d8752..5bd0cd3f 100644 --- a/api_list.include.md +++ b/api_list.include.md @@ -1494,6 +1494,14 @@ * `ValueTask CompletedTask` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.valuetask.completedtask?view=net-11.0) +#### Volatile + + * `void ReadBarrier()` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.threading.volatile.readbarrier?view=net-11.0) + * Note: Implemented as a full fence, since no one way barrier exists below net10. Correct but stronger than required, and unlike the BCL it costs a processor instruction on x86 and x64. + * `void WriteBarrier()` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.threading.volatile.writebarrier?view=net-11.0) + * Note: Implemented as a full fence, since no one way barrier exists below net10. Correct but stronger than required, and unlike the BCL it costs a processor instruction on x86 and x64. + + #### XDocument * `Task SaveAsync(Stream, SaveOptions, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.xml.linq.xdocument.saveasync?view=net-11.0#system-xml-linq-xdocument-saveasync(system-io-stream-system-xml-linq-saveoptions-system-threading-cancellationtoken)) diff --git a/assemblySize.include.md b/assemblySize.include.md index 7fddf141..49b0eb70 100644 --- a/assemblySize.include.md +++ b/assemblySize.include.md @@ -2,25 +2,25 @@ | | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability | |----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------| -| netstandard2.0 | 8.0KB | 371.0KB | +363.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netstandard2.1 | 8.5KB | 326.5KB | +318.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net461 | 8.5KB | 370.0KB | +361.5KB | +8.5KB | +6.5KB | +9.0KB | +13.0KB | -| net462 | 7.0KB | 375.0KB | +368.0KB | +7.0KB | +6.5KB | +7.5KB | +11.5KB | -| net47 | 7.0KB | 374.5KB | +367.5KB | +7.5KB | +6.5KB | +7.5KB | +12.0KB | -| net471 | 8.5KB | 372.5KB | +364.0KB | +8.5KB | +6.0KB | +8.5KB | +13.0KB | -| net472 | 8.5KB | 371.0KB | +362.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | -| net48 | 8.5KB | 371.0KB | +362.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | -| net481 | 8.5KB | 371.0KB | +362.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp2.0 | 9.0KB | 350.0KB | +341.0KB | +7.5KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp2.1 | 9.0KB | 330.0KB | +321.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp2.2 | 9.0KB | 330.0KB | +321.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp3.0 | 9.5KB | 326.0KB | +316.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp3.1 | 9.5KB | 324.0KB | +314.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | -| net5.0 | 9.5KB | 288.0KB | +278.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | -| net6.0 | 10.0KB | 230.0KB | +220.0KB | +9.5KB | +6.5KB | +512bytes | +3.0KB | -| net7.0 | 10.0KB | 196.0KB | +186.0KB | +9.5KB | +6.0KB | +512bytes | +3.5KB | -| net8.0 | 9.5KB | 166.0KB | +156.5KB | +8.5KB | +512bytes | +512bytes | +3.5KB | -| net9.0 | 9.5KB | 115.5KB | +106.0KB | +8.5KB | | +512bytes | +3.0KB | +| netstandard2.0 | 8.0KB | 371.5KB | +363.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netstandard2.1 | 8.5KB | 327.0KB | +318.5KB | +8.5KB | +6.5KB | +8.5KB | +13.5KB | +| net461 | 8.5KB | 370.0KB | +361.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| net462 | 7.0KB | 375.0KB | +368.0KB | +7.5KB | +6.5KB | +7.5KB | +12.0KB | +| net47 | 7.0KB | 375.0KB | +368.0KB | +7.5KB | +6.5KB | +7.5KB | +12.0KB | +| net471 | 8.5KB | 372.5KB | +364.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| net472 | 8.5KB | 371.5KB | +363.0KB | +8.5KB | +6.5KB | +9.0KB | +13.0KB | +| net48 | 8.5KB | 371.5KB | +363.0KB | +8.5KB | +6.5KB | +9.0KB | +13.0KB | +| net481 | 8.5KB | 371.5KB | +363.0KB | +8.5KB | +6.5KB | +9.0KB | +13.0KB | +| netcoreapp2.0 | 9.0KB | 350.5KB | +341.5KB | +7.5KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp2.1 | 9.0KB | 330.5KB | +321.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp2.2 | 9.0KB | 330.5KB | +321.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp3.0 | 9.5KB | 326.5KB | +317.0KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp3.1 | 9.5KB | 324.5KB | +315.0KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | +| net5.0 | 9.5KB | 288.5KB | +279.0KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | +| net6.0 | 10.0KB | 230.0KB | +220.0KB | +10.0KB | +7.0KB | +1.0KB | +3.5KB | +| net7.0 | 10.0KB | 196.5KB | +186.5KB | +9.5KB | +5.5KB | +512bytes | +3.5KB | +| net8.0 | 9.5KB | 166.5KB | +157.0KB | +8.5KB | +512bytes | +512bytes | +3.5KB | +| net9.0 | 9.5KB | 116.0KB | +106.5KB | +8.0KB | | +512bytes | +3.0KB | | net10.0 | 10.0KB | 93.5KB | +83.5KB | +8.5KB | | +512bytes | +3.0KB | | net11.0 | 10.0KB | 21.0KB | +11.0KB | +9.0KB | | +512bytes | +3.5KB | @@ -29,24 +29,24 @@ | | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability | |----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------| -| netstandard2.0 | 8.0KB | 544.1KB | +536.1KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netstandard2.1 | 8.5KB | 473.0KB | +464.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net461 | 8.5KB | 544.1KB | +535.6KB | +16.2KB | +8.2KB | +13.9KB | +18.4KB | -| net462 | 7.0KB | 549.1KB | +542.1KB | +14.7KB | +8.2KB | +12.4KB | +16.9KB | -| net47 | 7.0KB | 548.4KB | +541.4KB | +15.2KB | +8.2KB | +12.4KB | +17.4KB | -| net471 | 8.5KB | 546.0KB | +537.5KB | +16.2KB | +7.7KB | +13.4KB | +18.4KB | -| net472 | 8.5KB | 543.4KB | +534.9KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | -| net48 | 8.5KB | 543.4KB | +534.9KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | -| net481 | 8.5KB | 543.4KB | +534.9KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.0 | 9.0KB | 512.5KB | +503.5KB | +15.2KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.1 | 9.0KB | 480.2KB | +471.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.2 | 9.0KB | 480.2KB | +471.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp3.0 | 9.5KB | 469.5KB | +460.0KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp3.1 | 9.5KB | 467.5KB | +458.0KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | -| net5.0 | 9.5KB | 413.4KB | +403.9KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | -| net6.0 | 10.0KB | 335.4KB | +325.4KB | +17.2KB | +8.2KB | +1.1KB | +3.7KB | -| net7.0 | 10.0KB | 283.6KB | +273.6KB | +17.1KB | +7.4KB | +1.1KB | +4.2KB | -| net8.0 | 9.5KB | 239.8KB | +230.3KB | +16.0KB | +811bytes | +1.1KB | +4.2KB | -| net9.0 | 9.5KB | 166.8KB | +157.3KB | +16.0KB | | +1.1KB | +3.7KB | +| netstandard2.0 | 8.0KB | 544.9KB | +536.9KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netstandard2.1 | 8.5KB | 473.8KB | +465.3KB | +16.2KB | +8.2KB | +13.4KB | +18.9KB | +| net461 | 8.5KB | 544.5KB | +536.0KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| net462 | 7.0KB | 549.5KB | +542.5KB | +15.2KB | +8.2KB | +12.4KB | +17.4KB | +| net47 | 7.0KB | 549.2KB | +542.2KB | +15.2KB | +8.2KB | +12.4KB | +17.4KB | +| net471 | 8.5KB | 546.4KB | +537.9KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| net472 | 8.5KB | 544.3KB | +535.8KB | +16.2KB | +8.2KB | +13.9KB | +18.4KB | +| net48 | 8.5KB | 544.3KB | +535.8KB | +16.2KB | +8.2KB | +13.9KB | +18.4KB | +| net481 | 8.5KB | 544.3KB | +535.8KB | +16.2KB | +8.2KB | +13.9KB | +18.4KB | +| netcoreapp2.0 | 9.0KB | 513.4KB | +504.4KB | +15.2KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp2.1 | 9.0KB | 481.0KB | +472.0KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp2.2 | 9.0KB | 481.0KB | +472.0KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp3.0 | 9.5KB | 470.3KB | +460.8KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp3.1 | 9.5KB | 468.3KB | +458.8KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | +| net5.0 | 9.5KB | 414.2KB | +404.7KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | +| net6.0 | 10.0KB | 335.8KB | +325.8KB | +17.7KB | +8.7KB | +1.6KB | +4.2KB | +| net7.0 | 10.0KB | 284.4KB | +274.4KB | +17.1KB | +6.9KB | +1.1KB | +4.2KB | +| net8.0 | 9.5KB | 240.7KB | +231.2KB | +16.0KB | +811bytes | +1.1KB | +4.2KB | +| net9.0 | 9.5KB | 167.6KB | +158.1KB | +15.5KB | | +1.1KB | +3.7KB | | net10.0 | 10.0KB | 136.1KB | +126.1KB | +16.0KB | | +1.1KB | +3.7KB | | net11.0 | 10.0KB | 30.9KB | +20.9KB | +16.5KB | | +1.1KB | +4.2KB | diff --git a/readme.md b/readme.md index e40fa0f2..97f7e593 100644 --- a/readme.md +++ b/readme.md @@ -13,34 +13,34 @@ The package targets `netstandard2.0` and is designed to support the following ru * `uap10` -**API count: 1109** +**API count: 1111** ### Per Target Framework | Target | APIs | | -- | -- | -| `net461` | 1036 | -| `net462` | 1036 | -| `net47` | 1035 | -| `net471` | 1034 | -| `net472` | 1030 | -| `net48` | 1030 | -| `net481` | 1030 | -| `netstandard2.0` | 1032 | -| `netstandard2.1` | 885 | -| `netcoreapp2.0` | 955 | -| `netcoreapp2.1` | 896 | -| `netcoreapp2.2` | 896 | -| `netcoreapp3.0` | 857 | -| `netcoreapp3.1` | 856 | -| `net5.0` | 726 | -| `net6.0` | 627 | -| `net7.0` | 478 | -| `net8.0` | 357 | -| `net9.0` | 236 | +| `net461` | 1038 | +| `net462` | 1038 | +| `net47` | 1037 | +| `net471` | 1036 | +| `net472` | 1032 | +| `net48` | 1032 | +| `net481` | 1032 | +| `netstandard2.0` | 1034 | +| `netstandard2.1` | 887 | +| `netcoreapp2.0` | 957 | +| `netcoreapp2.1` | 898 | +| `netcoreapp2.2` | 898 | +| `netcoreapp3.0` | 859 | +| `netcoreapp3.1` | 858 | +| `net5.0` | 728 | +| `net6.0` | 629 | +| `net7.0` | 480 | +| `net8.0` | 359 | +| `net9.0` | 238 | | `net10.0` | 182 | | `net11.0` | 58 | -| `uap10.0` | 1022 | +| `uap10.0` | 1024 | @@ -96,25 +96,25 @@ This project uses features from the newest stable SDK and C# language. As such c | | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability | |----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------| -| netstandard2.0 | 8.0KB | 371.0KB | +363.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netstandard2.1 | 8.5KB | 326.5KB | +318.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net461 | 8.5KB | 370.0KB | +361.5KB | +8.5KB | +6.5KB | +9.0KB | +13.0KB | -| net462 | 7.0KB | 375.0KB | +368.0KB | +7.0KB | +6.5KB | +7.5KB | +11.5KB | -| net47 | 7.0KB | 374.5KB | +367.5KB | +7.5KB | +6.5KB | +7.5KB | +12.0KB | -| net471 | 8.5KB | 372.5KB | +364.0KB | +8.5KB | +6.0KB | +8.5KB | +13.0KB | -| net472 | 8.5KB | 371.0KB | +362.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | -| net48 | 8.5KB | 371.0KB | +362.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | -| net481 | 8.5KB | 371.0KB | +362.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp2.0 | 9.0KB | 350.0KB | +341.0KB | +7.5KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp2.1 | 9.0KB | 330.0KB | +321.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp2.2 | 9.0KB | 330.0KB | +321.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp3.0 | 9.5KB | 326.0KB | +316.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp3.1 | 9.5KB | 324.0KB | +314.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | -| net5.0 | 9.5KB | 288.0KB | +278.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | -| net6.0 | 10.0KB | 230.0KB | +220.0KB | +9.5KB | +6.5KB | +512bytes | +3.0KB | -| net7.0 | 10.0KB | 196.0KB | +186.0KB | +9.5KB | +6.0KB | +512bytes | +3.5KB | -| net8.0 | 9.5KB | 166.0KB | +156.5KB | +8.5KB | +512bytes | +512bytes | +3.5KB | -| net9.0 | 9.5KB | 115.5KB | +106.0KB | +8.5KB | | +512bytes | +3.0KB | +| netstandard2.0 | 8.0KB | 371.5KB | +363.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netstandard2.1 | 8.5KB | 327.0KB | +318.5KB | +8.5KB | +6.5KB | +8.5KB | +13.5KB | +| net461 | 8.5KB | 370.0KB | +361.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| net462 | 7.0KB | 375.0KB | +368.0KB | +7.5KB | +6.5KB | +7.5KB | +12.0KB | +| net47 | 7.0KB | 375.0KB | +368.0KB | +7.5KB | +6.5KB | +7.5KB | +12.0KB | +| net471 | 8.5KB | 372.5KB | +364.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| net472 | 8.5KB | 371.5KB | +363.0KB | +8.5KB | +6.5KB | +9.0KB | +13.0KB | +| net48 | 8.5KB | 371.5KB | +363.0KB | +8.5KB | +6.5KB | +9.0KB | +13.0KB | +| net481 | 8.5KB | 371.5KB | +363.0KB | +8.5KB | +6.5KB | +9.0KB | +13.0KB | +| netcoreapp2.0 | 9.0KB | 350.5KB | +341.5KB | +7.5KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp2.1 | 9.0KB | 330.5KB | +321.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp2.2 | 9.0KB | 330.5KB | +321.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp3.0 | 9.5KB | 326.5KB | +317.0KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp3.1 | 9.5KB | 324.5KB | +315.0KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | +| net5.0 | 9.5KB | 288.5KB | +279.0KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | +| net6.0 | 10.0KB | 230.0KB | +220.0KB | +10.0KB | +7.0KB | +1.0KB | +3.5KB | +| net7.0 | 10.0KB | 196.5KB | +186.5KB | +9.5KB | +5.5KB | +512bytes | +3.5KB | +| net8.0 | 9.5KB | 166.5KB | +157.0KB | +8.5KB | +512bytes | +512bytes | +3.5KB | +| net9.0 | 9.5KB | 116.0KB | +106.5KB | +8.0KB | | +512bytes | +3.0KB | | net10.0 | 10.0KB | 93.5KB | +83.5KB | +8.5KB | | +512bytes | +3.0KB | | net11.0 | 10.0KB | 21.0KB | +11.0KB | +9.0KB | | +512bytes | +3.5KB | @@ -123,25 +123,25 @@ This project uses features from the newest stable SDK and C# language. As such c | | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability | |----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------| -| netstandard2.0 | 8.0KB | 544.1KB | +536.1KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netstandard2.1 | 8.5KB | 473.0KB | +464.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net461 | 8.5KB | 544.1KB | +535.6KB | +16.2KB | +8.2KB | +13.9KB | +18.4KB | -| net462 | 7.0KB | 549.1KB | +542.1KB | +14.7KB | +8.2KB | +12.4KB | +16.9KB | -| net47 | 7.0KB | 548.4KB | +541.4KB | +15.2KB | +8.2KB | +12.4KB | +17.4KB | -| net471 | 8.5KB | 546.0KB | +537.5KB | +16.2KB | +7.7KB | +13.4KB | +18.4KB | -| net472 | 8.5KB | 543.4KB | +534.9KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | -| net48 | 8.5KB | 543.4KB | +534.9KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | -| net481 | 8.5KB | 543.4KB | +534.9KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.0 | 9.0KB | 512.5KB | +503.5KB | +15.2KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.1 | 9.0KB | 480.2KB | +471.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.2 | 9.0KB | 480.2KB | +471.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp3.0 | 9.5KB | 469.5KB | +460.0KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp3.1 | 9.5KB | 467.5KB | +458.0KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | -| net5.0 | 9.5KB | 413.4KB | +403.9KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | -| net6.0 | 10.0KB | 335.4KB | +325.4KB | +17.2KB | +8.2KB | +1.1KB | +3.7KB | -| net7.0 | 10.0KB | 283.6KB | +273.6KB | +17.1KB | +7.4KB | +1.1KB | +4.2KB | -| net8.0 | 9.5KB | 239.8KB | +230.3KB | +16.0KB | +811bytes | +1.1KB | +4.2KB | -| net9.0 | 9.5KB | 166.8KB | +157.3KB | +16.0KB | | +1.1KB | +3.7KB | +| netstandard2.0 | 8.0KB | 544.9KB | +536.9KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netstandard2.1 | 8.5KB | 473.8KB | +465.3KB | +16.2KB | +8.2KB | +13.4KB | +18.9KB | +| net461 | 8.5KB | 544.5KB | +536.0KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| net462 | 7.0KB | 549.5KB | +542.5KB | +15.2KB | +8.2KB | +12.4KB | +17.4KB | +| net47 | 7.0KB | 549.2KB | +542.2KB | +15.2KB | +8.2KB | +12.4KB | +17.4KB | +| net471 | 8.5KB | 546.4KB | +537.9KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| net472 | 8.5KB | 544.3KB | +535.8KB | +16.2KB | +8.2KB | +13.9KB | +18.4KB | +| net48 | 8.5KB | 544.3KB | +535.8KB | +16.2KB | +8.2KB | +13.9KB | +18.4KB | +| net481 | 8.5KB | 544.3KB | +535.8KB | +16.2KB | +8.2KB | +13.9KB | +18.4KB | +| netcoreapp2.0 | 9.0KB | 513.4KB | +504.4KB | +15.2KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp2.1 | 9.0KB | 481.0KB | +472.0KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp2.2 | 9.0KB | 481.0KB | +472.0KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp3.0 | 9.5KB | 470.3KB | +460.8KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp3.1 | 9.5KB | 468.3KB | +458.8KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | +| net5.0 | 9.5KB | 414.2KB | +404.7KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | +| net6.0 | 10.0KB | 335.8KB | +325.8KB | +17.7KB | +8.7KB | +1.6KB | +4.2KB | +| net7.0 | 10.0KB | 284.4KB | +274.4KB | +17.1KB | +6.9KB | +1.1KB | +4.2KB | +| net8.0 | 9.5KB | 240.7KB | +231.2KB | +16.0KB | +811bytes | +1.1KB | +4.2KB | +| net9.0 | 9.5KB | 167.6KB | +158.1KB | +15.5KB | | +1.1KB | +3.7KB | | net10.0 | 10.0KB | 136.1KB | +126.1KB | +16.0KB | | +1.1KB | +3.7KB | | net11.0 | 10.0KB | 30.9KB | +20.9KB | +16.5KB | | +1.1KB | +4.2KB | @@ -2109,6 +2109,14 @@ The class `Polyfill` includes the following extension methods: * `ValueTask CompletedTask` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.valuetask.completedtask?view=net-11.0) +#### Volatile + + * `void ReadBarrier()` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.threading.volatile.readbarrier?view=net-11.0) + * Note: Implemented as a full fence, since no one way barrier exists below net10. Correct but stronger than required, and unlike the BCL it costs a processor instruction on x86 and x64. + * `void WriteBarrier()` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.threading.volatile.writebarrier?view=net-11.0) + * Note: Implemented as a full fence, since no one way barrier exists below net10. Correct but stronger than required, and unlike the BCL it costs a processor instruction on x86 and x64. + + #### XDocument * `Task SaveAsync(Stream, SaveOptions, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.xml.linq.xdocument.saveasync?view=net-11.0#system-xml-linq-xdocument-saveasync(system-io-stream-system-xml-linq-saveoptions-system-threading-cancellationtoken)) @@ -2497,7 +2505,7 @@ void ObjectDisposedExceptionExample(bool isDisposed) ObjectDisposedException.ThrowIf(isDisposed, typeof(Consume)); } ``` -snippet source | anchor +snippet source | anchor @@ -2516,7 +2524,7 @@ void EnsureExample(Order order, Customer customer, string customerId, string ema this.quantity = Ensure.NotNegativeOrZero(quantity); } ``` -snippet source | anchor +snippet source | anchor diff --git a/src/Consume/Consume.cs b/src/Consume/Consume.cs index f9df0ab2..c34537ef 100644 --- a/src/Consume/Consume.cs +++ b/src/Consume/Consume.cs @@ -326,6 +326,12 @@ void Base64Usage() } #endif + void VolatileBarrierUsage() + { + Volatile.ReadBarrier(); + Volatile.WriteBarrier(); + } + void UriSchemeDataUsage() { _ = Uri.UriSchemeData; diff --git a/src/Polyfill/VolatilePolyfill.cs b/src/Polyfill/VolatilePolyfill.cs new file mode 100644 index 00000000..cb7246ca --- /dev/null +++ b/src/Polyfill/VolatilePolyfill.cs @@ -0,0 +1,33 @@ +#if !NET10_0_OR_GREATER + +namespace Polyfills; + +using System.Threading; + +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.threading.volatile.readbarrier?view=net-11.0 + //Note: Implemented as a full fence, since no one way barrier exists below net10. Correct but stronger than required, and unlike the BCL it costs a processor instruction on x86 and x64. + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.threading.volatile.writebarrier?view=net-11.0 + //Note: Implemented as a full fence, since no one way barrier exists below net10. Correct but stronger than required, and unlike the BCL it costs a processor instruction on x86 and x64. + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} + +#endif diff --git a/src/Split/net461/VolatilePolyfill.cs b/src/Split/net461/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/net461/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/net462/VolatilePolyfill.cs b/src/Split/net462/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/net462/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/net47/VolatilePolyfill.cs b/src/Split/net47/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/net47/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/net471/VolatilePolyfill.cs b/src/Split/net471/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/net471/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/net472/VolatilePolyfill.cs b/src/Split/net472/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/net472/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/net48/VolatilePolyfill.cs b/src/Split/net48/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/net48/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/net481/VolatilePolyfill.cs b/src/Split/net481/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/net481/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/net5.0/VolatilePolyfill.cs b/src/Split/net5.0/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/net5.0/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/net6.0/VolatilePolyfill.cs b/src/Split/net6.0/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/net6.0/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/net7.0/VolatilePolyfill.cs b/src/Split/net7.0/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/net7.0/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/net8.0/VolatilePolyfill.cs b/src/Split/net8.0/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/net8.0/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/net9.0/VolatilePolyfill.cs b/src/Split/net9.0/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/net9.0/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/netcoreapp2.0/VolatilePolyfill.cs b/src/Split/netcoreapp2.0/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/netcoreapp2.0/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/netcoreapp2.1/VolatilePolyfill.cs b/src/Split/netcoreapp2.1/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/netcoreapp2.1/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/netcoreapp2.2/VolatilePolyfill.cs b/src/Split/netcoreapp2.2/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/netcoreapp2.2/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/netcoreapp3.0/VolatilePolyfill.cs b/src/Split/netcoreapp3.0/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/netcoreapp3.0/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/netcoreapp3.1/VolatilePolyfill.cs b/src/Split/netcoreapp3.1/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/netcoreapp3.1/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/netstandard2.0/VolatilePolyfill.cs b/src/Split/netstandard2.0/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/netstandard2.0/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/netstandard2.1/VolatilePolyfill.cs b/src/Split/netstandard2.1/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/netstandard2.1/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Split/uap10.0/VolatilePolyfill.cs b/src/Split/uap10.0/VolatilePolyfill.cs new file mode 100644 index 00000000..ff930b0a --- /dev/null +++ b/src/Split/uap10.0/VolatilePolyfill.cs @@ -0,0 +1,24 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Threading; +static partial class Polyfill +{ + extension(Volatile) + { + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory reads before the call to ReadBarrier execute after memory + /// accesses that follow the call to ReadBarrier. + /// + public static void ReadBarrier() => + Interlocked.MemoryBarrier(); + /// + /// Synchronizes memory access as follows: the processor that executes the current thread cannot reorder + /// instructions in such a way that memory accesses before the call to WriteBarrier execute after + /// memory writes that follow the call to WriteBarrier. + /// + public static void WriteBarrier() => + Interlocked.MemoryBarrier(); + } +} diff --git a/src/Tests/VolatilePolyfillTests.cs b/src/Tests/VolatilePolyfillTests.cs new file mode 100644 index 00000000..9ec0b48b --- /dev/null +++ b/src/Tests/VolatilePolyfillTests.cs @@ -0,0 +1,78 @@ +using System.Diagnostics; +using System.Threading; + +// Memory ordering cannot be asserted deterministically from a unit test: on x86 and x64 the +// message passing case below succeeds even with no barrier at all. These exercise the calls in the +// pattern they exist for, which catches a barrier that throws or fails to compile on a target, and +// leave the ordering guarantee itself to the implementation. +public class VolatilePolyfillTests +{ + [Test] + public async Task Barriers_CanBeCalled() + { + Volatile.ReadBarrier(); + Volatile.WriteBarrier(); + + await Assert.That(true).IsTrue(); + } + + [Test] + public async Task Barriers_MessagePassing() + { + const int iterations = 200; + + var payload = 0; + var published = 0; + var observed = new int[iterations]; + + var writer = Task.Run( + () => + { + for (var i = 1; i <= iterations; i++) + { + payload = i; + Volatile.WriteBarrier(); + Volatile.Write(ref published, i); + } + }); + + var reader = Task.Run( + () => + { + var watch = Stopwatch.StartNew(); + for (var i = 1; i <= iterations; i++) + { + var spin = new SpinWait(); + while (Volatile.Read(ref published) < i) + { + if (watch.Elapsed > TimeSpan.FromSeconds(20)) + { + return; + } + + spin.SpinOnce(); + } + + Volatile.ReadBarrier(); + observed[i - 1] = payload; + } + }); + + var completed = Task.WaitAll(new[] { writer, reader }, TimeSpan.FromSeconds(30)); + + await Assert.That(completed).IsTrue(); + + // the reader never sees a payload older than the one it waited to be published + var stale = -1; + for (var i = 0; i < iterations; i++) + { + if (observed[i] < i + 1) + { + stale = i; + break; + } + } + + await Assert.That(stale).IsEqualTo(-1); + } +}