Skip to content

Commit 4071a42

Browse files
committed
Release 5.12.1
1 parent a2527d7 commit 4071a42

File tree

9 files changed

+235
-37
lines changed

9 files changed

+235
-37
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
Release Notes
22
====
33

4+
# 08-19-2024
5+
<a href="https://www.nuget.org/packages/dotnext/5.12.1">DotNext 5.12.1</a>
6+
* Added support of static field references to `DotNext.Runtime.ValueReference<T>` data type
7+
8+
<a href="https://www.nuget.org/packages/dotnext.threading/5.12.1">DotNext.Threading 5.12.1</a>
9+
* Smallish performance improvements of `RandomAccessCache<TKey, TValue>` class
10+
411
# 08-13-2024
512
<a href="https://www.nuget.org/packages/dotnext/5.12.0">DotNext 5.12.0</a>
613
* Added `DotNext.Runtime.ValueReference<T>` data type that allows to obtain async-friendly managed pointer to the field

README.md

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,29 +44,13 @@ All these things are implemented in 100% managed code on top of existing .NET AP
4444
* [NuGet Packages](https://www.nuget.org/profiles/rvsakno)
4545

4646
# What's new
47-
Release Date: 08-14-2024
47+
Release Date: 08-19-2024
4848

49-
<a href="https://www.nuget.org/packages/dotnext/5.12.0">DotNext 5.12.0</a>
50-
* Added `DotNext.Runtime.ValueReference<T>` data type that allows to obtain async-friendly managed pointer to the field
51-
* Deprecation of `ConcurrentCache<TKey, TValue>` in favor of `RandomAccessCache<TKey, TValue>`
49+
<a href="https://www.nuget.org/packages/dotnext/5.12.1">DotNext 5.12.1</a>
50+
* Added support of static field references to `DotNext.Runtime.ValueReference<T>` data type
5251

53-
<a href="https://www.nuget.org/packages/dotnext.metaprogramming/5.12.0">DotNext.Metaprogramming 5.12.0</a>
54-
* Updated dependencies
55-
56-
<a href="https://www.nuget.org/packages/dotnext.unsafe/5.12.0">DotNext.Unsafe 5.12.0</a>
57-
* Updated dependencies
58-
59-
<a href="https://www.nuget.org/packages/dotnext.threading/5.12.0">DotNext.Threading 5.12.0</a>
60-
* Introduced async-friendly `RandomAccessCache<TKey, TValue>` as a replacement for deprecated `ConcurrentCache<TKey, TValue>`. It uses [SIEVE](https://cachemon.github.io/SIEVE-website/) eviction algorithm.
61-
62-
<a href="https://www.nuget.org/packages/dotnext.io/5.12.0">DotNext.IO 5.12.0</a>
63-
* Updated dependencies
64-
65-
<a href="https://www.nuget.org/packages/dotnext.net.cluster/5.12.0">DotNext.Net.Cluster 5.12.0</a>
66-
* Fixed cancellation of `PersistentState` async methods
67-
68-
<a href="https://www.nuget.org/packages/dotnext.aspnetcore.cluster/5.12.0">DotNext.AspNetCore.Cluster 5.12.0</a>
69-
* Fixed cancellation of `PersistentState` async methods
52+
<a href="https://www.nuget.org/packages/dotnext.threading/5.12.1">DotNext.Threading 5.12.1</a>
53+
* Smallish performance improvements of `RandomAccessCache<TKey, TValue>` class
7054

7155
Changelog for previous versions located [here](./CHANGELOG.md).
7256

src/DotNext.Tests/Runtime/ValueReferenceTests.cs

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Runtime.CompilerServices;
2+
using System.Runtime.InteropServices;
23

34
namespace DotNext.Runtime;
45

@@ -84,18 +85,121 @@ public static void MutableEmptyRef()
8485
var reference = default(ValueReference<float>);
8586
True(reference.IsEmpty);
8687
Null(reference.ToString());
88+
89+
Span<float> span = reference;
90+
True(span.IsEmpty);
8791
}
88-
92+
8993
[Fact]
9094
public static void ImmutableEmptyRef()
9195
{
9296
var reference = default(ReadOnlyValueReference<float>);
9397
True(reference.IsEmpty);
9498
Null(reference.ToString());
99+
100+
ReadOnlySpan<float> span = reference;
101+
True(span.IsEmpty);
102+
}
103+
104+
[Fact]
105+
public static void AnonymousValue()
106+
{
107+
var reference = new ValueReference<int>(42);
108+
Equal(42, reference.Value);
109+
110+
ReadOnlyValueReference<int> roRef = reference;
111+
Equal(42, roRef.Value);
112+
}
113+
114+
[Fact]
115+
public static void StaticObjectAccess()
116+
{
117+
var reference = new ValueReference<string>(ref MyClass.StaticObject)
118+
{
119+
Value = "Hello, world",
120+
};
121+
122+
GC.Collect(3, GCCollectionMode.Forced, true, true);
123+
GC.WaitForPendingFinalizers();
124+
125+
True(reference == new ValueReference<string>(ref MyClass.StaticObject));
126+
Same(MyClass.StaticObject, reference.Value);
127+
}
128+
129+
[Fact]
130+
public static void StaticValueTypeAccess()
131+
{
132+
var reference = new ReadOnlyValueReference<int>(in MyClass.StaticValueType);
133+
MyClass.StaticValueType = 42;
134+
135+
GC.Collect(3, GCCollectionMode.Forced, true, true);
136+
GC.WaitForPendingFinalizers();
137+
138+
True(reference == new ReadOnlyValueReference<int>(in MyClass.StaticValueType));
139+
Equal(MyClass.StaticValueType, reference.Value);
140+
}
141+
142+
[Fact]
143+
public static void IncorrectReference()
144+
{
145+
byte[] empty = [];
146+
Throws<ArgumentOutOfRangeException>(() => new ValueReference<byte>(empty, ref MemoryMarshal.GetArrayDataReference(empty)));
147+
Throws<ArgumentOutOfRangeException>(() => new ReadOnlyValueReference<byte>(empty, ref MemoryMarshal.GetArrayDataReference(empty)));
148+
}
149+
150+
[Fact]
151+
public static void ReferenceSize()
152+
{
153+
Equal(Unsafe.SizeOf<ValueReference<float>>(), nint.Size + nint.Size);
154+
}
155+
156+
[Fact]
157+
public static void BoxedValueInterop()
158+
{
159+
var boxedInt = BoxedValue<int>.Box(42);
160+
ValueReference<int> reference = boxedInt;
161+
162+
boxedInt.Value = 56;
163+
Equal(boxedInt.Value, reference.Value);
164+
}
165+
166+
[Fact]
167+
public static void ArrayCovariance()
168+
{
169+
string[] array = ["a", "b"];
170+
Throws<ArrayTypeMismatchException>(() => new ValueReference<object>(array, 0));
171+
172+
var roRef = new ReadOnlyValueReference<object>(array, 1);
173+
Equal("b", roRef.Value);
174+
}
175+
176+
[Fact]
177+
public static void SpanInterop()
178+
{
179+
var reference = new ValueReference<int>(42);
180+
Span<int> span = reference;
181+
Equal(1, span.Length);
182+
183+
True(Unsafe.AreSame(in reference.Value, in span[0]));
184+
}
185+
186+
[Fact]
187+
public static void ReadOnlySpanInterop()
188+
{
189+
ReadOnlyValueReference<int> reference = new ValueReference<int>(42);
190+
ReadOnlySpan<int> span = reference;
191+
Equal(1, span.Length);
192+
193+
True(Unsafe.AreSame(in reference.Value, in span[0]));
95194
}
96195

97196
private record class MyClass : IResettable
98197
{
198+
internal static string StaticObject;
199+
200+
[FixedAddressValueType]
201+
internal static int StaticValueType;
202+
99203
internal int Field;
100204
internal string AnotherField;
101205

src/DotNext.Threading/DotNext.Threading.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<ImplicitUsings>true</ImplicitUsings>
88
<IsTrimmable>true</IsTrimmable>
99
<Features>nullablePublicOnly</Features>
10-
<VersionPrefix>5.12.0</VersionPrefix>
10+
<VersionPrefix>5.12.1</VersionPrefix>
1111
<VersionSuffix></VersionSuffix>
1212
<Authors>.NET Foundation and Contributors</Authors>
1313
<Product>.NEXT Family of Libraries</Product>

src/DotNext.Threading/Runtime/Caching/RandomAccessCache.Eviction.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ private void Reset()
225225
{
226226
version++;
227227
continuationState = null;
228-
Interlocked.Exchange(ref continuation, null);
228+
continuation = null;
229229
}
230230

231231
ValueTaskSourceStatus IValueTaskSource<bool>.GetStatus(short token)

src/DotNext/DotNext.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<Authors>.NET Foundation and Contributors</Authors>
1212
<Company />
1313
<Product>.NEXT Family of Libraries</Product>
14-
<VersionPrefix>5.12.0</VersionPrefix>
14+
<VersionPrefix>5.12.1</VersionPrefix>
1515
<VersionSuffix></VersionSuffix>
1616
<AssemblyName>DotNext</AssemblyName>
1717
<PackageLicenseExpression>MIT</PackageLicenseExpression>

src/DotNext/Runtime/BoxedValue.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ namespace DotNext.Runtime;
101101
[return: NotNullIfNotNull(nameof(value))]
102102
public static explicit operator BoxedValue<T>?(in T? value) => TryBox(in value);
103103

104+
/// <summary>
105+
/// Obtains a reference to the boxed value.
106+
/// </summary>
107+
/// <param name="boxedValue">Boxed value.</param>
108+
/// <returns>Mutable reference to the boxed value.</returns>
109+
public static implicit operator ValueReference<T>(BoxedValue<T> boxedValue)
110+
=> new(boxedValue, ref boxedValue.Value);
111+
104112
/// <inheritdoc />
105113
public abstract override bool Equals([NotNullWhen(true)] object? obj); // abstract to avoid inlining by AOT/JIT
106114

src/DotNext/Runtime/ValueReference.cs

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
using System.ComponentModel;
12
using System.Diagnostics.CodeAnalysis;
23
using System.Numerics;
4+
using System.Reflection;
35
using System.Runtime.CompilerServices;
46
using System.Runtime.InteropServices;
57

@@ -12,6 +14,7 @@ namespace DotNext.Runtime;
1214
/// <param name="fieldRef">The reference to the field.</param>
1315
/// <typeparam name="T">The type of the field.</typeparam>
1416
[StructLayout(LayoutKind.Auto)]
17+
[EditorBrowsable(EditorBrowsableState.Advanced)]
1518
public readonly struct ValueReference<T>(object owner, ref T fieldRef) :
1619
IEquatable<ValueReference<T>>,
1720
IEqualityOperators<ValueReference<T>, ValueReference<T>, bool>
@@ -23,11 +26,46 @@ public readonly struct ValueReference<T>(object owner, ref T fieldRef) :
2326
/// </summary>
2427
/// <param name="array">The array.</param>
2528
/// <param name="index">The index of the array element.</param>
29+
/// <exception cref="ArrayTypeMismatchException">
30+
/// <typeparamref name="T"/> is a reference type, and <paramref name="array" /> is not an array of type <typeparamref name="T"/>.
31+
/// </exception>
2632
public ValueReference(T[] array, int index)
2733
: this(array, ref array[index])
2834
{
2935
}
3036

37+
private ValueReference(StrongBox<T> box)
38+
: this(box, ref box.Value!)
39+
{
40+
}
41+
42+
/// <summary>
43+
/// Creates a reference to the anonymous value.
44+
/// </summary>
45+
/// <param name="value">The anonymous value.</param>
46+
public ValueReference(T value)
47+
: this(new StrongBox<T> { Value = value })
48+
{
49+
}
50+
51+
/// <summary>
52+
/// Creates a reference to a static field.
53+
/// </summary>
54+
/// <remarks>
55+
/// If <typeparamref name="T"/> is a value type then your static field MUST be marked
56+
/// with <see cref="FixedAddressValueTypeAttribute"/>. Otherwise, the behavior is unpredictable.
57+
/// Correctness of this constructor is based on the fact that static fields are stored
58+
/// as elements of <see cref="object"/> array allocated by the runtime in the Pinned Object Heap.
59+
/// It means that the address of the field cannot be changed by GC.
60+
/// </remarks>
61+
/// <param name="staticFieldRef">A reference to the static field.</param>
62+
/// <seealso href="https://devblogs.microsoft.com/dotnet/internals-of-the-poh/">Internals of the POH</seealso>
63+
[CLSCompliant(false)]
64+
public ValueReference(ref T staticFieldRef)
65+
: this(Sentinel.Instance, ref staticFieldRef)
66+
{
67+
}
68+
3169
/// <summary>
3270
/// Gets a value indicating that is reference is empty.
3371
/// </summary>
@@ -80,21 +118,57 @@ public bool Equals(ValueReference<T> reference)
80118
/// <returns>The immutable field reference.</returns>
81119
public static implicit operator ReadOnlyValueReference<T>(ValueReference<T> reference)
82120
=> Unsafe.BitCast<ValueReference<T>, ReadOnlyValueReference<T>>(reference);
121+
122+
/// <summary>
123+
/// Gets a span over the referenced value.
124+
/// </summary>
125+
/// <param name="reference">The value reference.</param>
126+
/// <returns>The span that contains <see cref="Value"/>; or empty span if <paramref name="reference"/> is empty.</returns>
127+
public static implicit operator Span<T>(ValueReference<T> reference)
128+
=> reference.IsEmpty ? new() : new(ref reference.Value);
83129
}
84130

85131
/// <summary>
86-
/// Represents a mutable reference to the field.
132+
/// Represents an immutable reference to the field.
87133
/// </summary>
88134
/// <param name="owner">An object that owns the field.</param>
89135
/// <param name="fieldRef">The reference to the field.</param>
90136
/// <typeparam name="T">The type of the field.</typeparam>
91137
[StructLayout(LayoutKind.Auto)]
138+
[EditorBrowsable(EditorBrowsableState.Advanced)]
92139
public readonly struct ReadOnlyValueReference<T>(object owner, ref readonly T fieldRef) :
93140
IEquatable<ReadOnlyValueReference<T>>,
94141
IEqualityOperators<ReadOnlyValueReference<T>, ReadOnlyValueReference<T>, bool>
95142
{
96143
private readonly nint offset = RawData.GetOffset(owner, in fieldRef);
97144

145+
/// <summary>
146+
/// Creates a reference to an array element.
147+
/// </summary>
148+
/// <param name="array">The array.</param>
149+
/// <param name="index">The index of the array element.</param>
150+
public ReadOnlyValueReference(T[] array, int index)
151+
: this(array, in array[index])
152+
{
153+
}
154+
155+
/// <summary>
156+
/// Creates a reference to a static field.
157+
/// </summary>
158+
/// <remarks>
159+
/// If <typeparamref name="T"/> is a value type then your static field MUST be marked
160+
/// with <see cref="FixedAddressValueTypeAttribute"/>. Otherwise, the behavior is unpredictable.
161+
/// Correctness of this constructor is based on the fact that static fields are stored
162+
/// as elements of <see cref="object"/> array allocated by the runtime in the Pinned Object Heap.
163+
/// It means that the address of the field cannot be changed by GC.
164+
/// </remarks>
165+
/// <param name="staticFieldRef">A reference to the static field.</param>
166+
/// <seealso href="https://devblogs.microsoft.com/dotnet/internals-of-the-poh/">Internals of the POH</seealso>
167+
public ReadOnlyValueReference(ref readonly T staticFieldRef)
168+
: this(Sentinel.Instance, in staticFieldRef)
169+
{
170+
}
171+
98172
/// <summary>
99173
/// Gets a value indicating that is reference is empty.
100174
/// </summary>
@@ -139,19 +213,45 @@ public bool Equals(ReadOnlyValueReference<T> reference)
139213
/// <returns><see langword="true"/> if both references are not equal; otherwise, <see langword="false"/>.</returns>
140214
public static bool operator !=(ReadOnlyValueReference<T> x, ReadOnlyValueReference<T> y)
141215
=> x.Equals(y) is false;
216+
217+
/// <summary>
218+
/// Gets a span over the referenced value.
219+
/// </summary>
220+
/// <param name="reference">The value reference.</param>
221+
/// <returns>The span that contains <see cref="Value"/>; or empty span if <paramref name="reference"/> is empty.</returns>
222+
public static implicit operator ReadOnlySpan<T>(ReadOnlyValueReference<T> reference)
223+
=> reference.IsEmpty ? new() : new(in reference.Value);
142224
}
143225

144226
[SuppressMessage("Performance", "CA1812", Justification = "Used for reinterpret cast")]
145-
file sealed class RawData
227+
file abstract class RawData
146228
{
229+
// TODO: Replace with public counterpart in future
230+
private static readonly Func<object, nuint>? GetRawObjectDataSize;
231+
232+
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, typeof(RuntimeHelpers))]
233+
static RawData()
234+
{
235+
const BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Static;
236+
GetRawObjectDataSize = typeof(RuntimeHelpers)
237+
.GetMethod(nameof(GetRawObjectDataSize), flags, [typeof(object)])
238+
?.CreateDelegate<Func<object, nuint>>();
239+
}
240+
147241
private byte data;
148242

149243
private RawData() => throw new NotImplementedException();
150244

151-
internal static nint GetOffset<T>(object owner, ref readonly T field)
245+
internal static nint GetOffset<T>(object owner, ref readonly T field, [CallerArgumentExpression(nameof(field))] string? paramName = null)
152246
{
153247
ref var rawData = ref Unsafe.As<RawData>(owner).data;
154-
return Unsafe.ByteOffset(in rawData, in Intrinsics.ChangeType<T, byte>(in field));
248+
var offset = Unsafe.ByteOffset(in rawData, in Intrinsics.ChangeType<T, byte>(in field));
249+
250+
// Ensure that the reference is an interior pointer to the field inside the object
251+
if (GetRawObjectDataSize is not null && owner != Sentinel.Instance && (nuint)(offset + Unsafe.SizeOf<T>()) > GetRawObjectDataSize(owner))
252+
throw new ArgumentOutOfRangeException(paramName);
253+
254+
return offset;
155255
}
156256

157257
internal static ref T GetObjectData<T>(object owner, nint offset)

0 commit comments

Comments
 (0)