Skip to content

Commit 1c5ef76

Browse files
authored
Merge branch 'master' into muxtestinfra
2 parents dd52af5 + 49760c3 commit 1c5ef76

File tree

10 files changed

+92
-55
lines changed

10 files changed

+92
-55
lines changed

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@ A clear and concise description of what the bug is.
1818
- [ ] Is this bug a regression in the toolkit? If so, what toolkit version did you last see it work:
1919

2020
## Steps to Reproduce
21+
22+
- [ ] Can this be reproduced in the Sample App? (Either in a sample as-is or with new XAML pasted in the editor.) If so, please provide custom XAML or steps to reproduce. If not, let us know why it can't be reproduced (e.g. more complex setup, environment, dependencies, etc...) <!-- Being able to reproduce the problem in the sample app, really stream-lines the whole process in being able to discover, resolve, and validate bug fixes. -->
23+
2124
Steps to reproduce the behavior:
22-
1. Go to '...'
23-
2. Click on '....'
24-
3. Scroll down to '....'
25-
4. See error
25+
1. Given the following environment (Sample App w/ XAML, Project with Isolated setup, etc...)
26+
2. Go to '...'
27+
3. Click on '....'
28+
4. Scroll down to '....'
29+
5. See error
30+
31+
<!-- Provide as many code-snippets or XAML snippets where appropriate. -->
2632

2733
## Expected behavior
2834
A clear and concise description of what you expected to happen.

Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,13 @@ public static ref T DangerousGetReference<T>(this T[,] array)
6666
/// </remarks>
6767
[Pure]
6868
[MethodImpl(MethodImplOptions.AggressiveInlining)]
69-
public static ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j)
69+
public static unsafe ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j)
7070
{
7171
#if NETCORE_RUNTIME
7272
var arrayData = Unsafe.As<RawArray2DData>(array);
7373
int offset = (i * arrayData.Width) + j;
7474
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
75-
ref T ri = ref Unsafe.Add(ref r0, offset);
75+
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset);
7676

7777
return ref ri;
7878
#else
@@ -82,10 +82,7 @@ public static ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j)
8282
return ref array[i, j];
8383
}
8484

85-
unsafe
86-
{
87-
return ref Unsafe.AsRef<T>(null);
88-
}
85+
return ref Unsafe.AsRef<T>(null);
8986
#endif
9087
}
9188

@@ -274,11 +271,11 @@ public static Span<T> AsSpan<T>(this T[,] array)
274271
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="array"/>.</returns>
275272
[Pure]
276273
[MethodImpl(MethodImplOptions.AggressiveInlining)]
277-
public static int Count<T>(this T[,] array, T value)
274+
public static unsafe int Count<T>(this T[,] array, T value)
278275
where T : IEquatable<T>
279276
{
280277
ref T r0 = ref array.DangerousGetReference();
281-
IntPtr length = (IntPtr)array.Length;
278+
IntPtr length = (IntPtr)(void*)(uint)array.Length;
282279

283280
return SpanHelper.Count(ref r0, length, value);
284281
}
@@ -293,11 +290,11 @@ public static int Count<T>(this T[,] array, T value)
293290
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
294291
[Pure]
295292
[MethodImpl(MethodImplOptions.AggressiveInlining)]
296-
public static int GetDjb2HashCode<T>(this T[,] array)
293+
public static unsafe int GetDjb2HashCode<T>(this T[,] array)
297294
where T : notnull
298295
{
299296
ref T r0 = ref array.DangerousGetReference();
300-
IntPtr length = (IntPtr)array.Length;
297+
IntPtr length = (IntPtr)(void*)(uint)array.Length;
301298

302299
return SpanHelper.GetDjb2HashCode(ref r0, length);
303300
}

Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.cs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ public static ref T DangerousGetReference<T>(this T[] array)
6262
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
6363
[Pure]
6464
[MethodImpl(MethodImplOptions.AggressiveInlining)]
65-
public static ref T DangerousGetReferenceAt<T>(this T[] array, int i)
65+
public static unsafe ref T DangerousGetReferenceAt<T>(this T[] array, int i)
6666
{
6767
#if NETCORE_RUNTIME
6868
var arrayData = Unsafe.As<RawArrayData>(array);
6969
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
70-
ref T ri = ref Unsafe.Add(ref r0, i);
70+
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
7171

7272
return ref ri;
7373
#else
@@ -76,10 +76,7 @@ public static ref T DangerousGetReferenceAt<T>(this T[] array, int i)
7676
return ref array[i];
7777
}
7878

79-
unsafe
80-
{
81-
return ref Unsafe.AsRef<T>(null);
82-
}
79+
return ref Unsafe.AsRef<T>(null);
8380
#endif
8481
}
8582

@@ -114,11 +111,11 @@ private sealed class RawArrayData
114111
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="array"/>.</returns>
115112
[Pure]
116113
[MethodImpl(MethodImplOptions.AggressiveInlining)]
117-
public static int Count<T>(this T[] array, T value)
114+
public static unsafe int Count<T>(this T[] array, T value)
118115
where T : IEquatable<T>
119116
{
120117
ref T r0 = ref array.DangerousGetReference();
121-
IntPtr length = (IntPtr)array.Length;
118+
IntPtr length = (IntPtr)(void*)(uint)array.Length;
122119

123120
return SpanHelper.Count(ref r0, length, value);
124121
}
@@ -185,11 +182,11 @@ public static SpanTokenizer<T> Tokenize<T>(this T[] array, T separator)
185182
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
186183
[Pure]
187184
[MethodImpl(MethodImplOptions.AggressiveInlining)]
188-
public static int GetDjb2HashCode<T>(this T[] array)
185+
public static unsafe int GetDjb2HashCode<T>(this T[] array)
189186
where T : notnull
190187
{
191188
ref T r0 = ref array.DangerousGetReference();
192-
IntPtr length = (IntPtr)array.Length;
189+
IntPtr length = (IntPtr)(void*)(uint)array.Length;
193190

194191
return SpanHelper.GetDjb2HashCode(ref r0, length);
195192
}

Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,40 @@ public static ref T DangerousGetReference<T>(this ReadOnlySpan<T> span)
4040
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
4141
[Pure]
4242
[MethodImpl(MethodImplOptions.AggressiveInlining)]
43-
public static ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> span, int i)
43+
public static unsafe ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> span, int i)
4444
{
45+
// Here we assume the input index will never be negative, so we do an unsafe cast to
46+
// force the JIT to skip the sign extension when going from int to native int.
47+
// On .NET Core 3.1, if we only use Unsafe.Add(ref r0, i), we get the following:
48+
// =============================
49+
// L0000: mov rax, [rcx]
50+
// L0003: movsxd rdx, edx
51+
// L0006: lea rax, [rax+rdx*4]
52+
// L000a: ret
53+
// =============================
54+
// Note the movsxd (move with sign extension) to expand the index passed in edx to
55+
// the whole rdx register. This is unnecessary and more expensive than just a mov,
56+
// which when done to a large register size automatically zeroes the upper bits.
57+
// With the (IntPtr)(void*)(uint) cast, we get the following codegen instead:
58+
// =============================
59+
// L0000: mov rax, [rcx]
60+
// L0003: mov edx, edx
61+
// L0005: lea rax, [rax+rdx*4]
62+
// L0009: ret
63+
// =============================
64+
// Here we can see how the index is extended to a native integer with just a mov,
65+
// which effectively only zeroes the upper bits of the same register used as source.
66+
// These three casts are a bit verbose, but they do the trick on both 32 bit and 64
67+
// bit architectures, producing optimal code in both cases (they are either completely
68+
// elided on 32 bit systems, or result in the correct register expansion when on 64 bit).
69+
// We first do an unchecked conversion to uint (which is just a reinterpret-cast). We
70+
// then cast to void*, which lets the following IntPtr cast avoid the range check on 32 bit
71+
// (since uint could be out of range there if the original index was negative). The final
72+
// result is a clean mov as shown above. This will eventually be natively supported by the
73+
// JIT compiler (see https://github.com/dotnet/runtime/issues/38794), but doing this here
74+
// still ensures the optimal codegen even on existing runtimes (eg. .NET Core 2.1 and 3.1).
4575
ref T r0 = ref MemoryMarshal.GetReference(span);
46-
ref T ri = ref Unsafe.Add(ref r0, i);
76+
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
4777

4878
return ref ri;
4979
}
@@ -87,7 +117,7 @@ public static ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> span, int i)
87117
/// </returns>
88118
[Pure]
89119
[MethodImpl(MethodImplOptions.AggressiveInlining)]
90-
public static ref readonly T DangerousGetLookupReferenceAt<T>(this ReadOnlySpan<T> span, int i)
120+
public static unsafe ref readonly T DangerousGetLookupReferenceAt<T>(this ReadOnlySpan<T> span, int i)
91121
{
92122
// Check whether the input is in range by first casting both
93123
// operands to uint and then comparing them, as this allows
@@ -106,12 +136,12 @@ public static ref readonly T DangerousGetLookupReferenceAt<T>(this ReadOnlySpan<
106136
// lookup table can just be assumed to always be false.
107137
bool isInRange = (uint)i < (uint)span.Length;
108138
byte rangeFlag = Unsafe.As<bool, byte>(ref isInRange);
109-
int
110-
negativeFlag = rangeFlag - 1,
139+
uint
140+
negativeFlag = unchecked(rangeFlag - 1u),
111141
mask = ~negativeFlag,
112-
offset = i & mask;
142+
offset = (uint)i & mask;
113143
ref T r0 = ref MemoryMarshal.GetReference(span);
114-
ref T r1 = ref Unsafe.Add(ref r0, offset);
144+
ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)offset);
115145

116146
return ref r1;
117147
}
@@ -165,11 +195,11 @@ public static unsafe int IndexOf<T>(this ReadOnlySpan<T> span, in T value)
165195
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
166196
[Pure]
167197
[MethodImpl(MethodImplOptions.AggressiveInlining)]
168-
public static int Count<T>(this ReadOnlySpan<T> span, T value)
198+
public static unsafe int Count<T>(this ReadOnlySpan<T> span, T value)
169199
where T : IEquatable<T>
170200
{
171201
ref T r0 = ref MemoryMarshal.GetReference(span);
172-
IntPtr length = (IntPtr)span.Length;
202+
IntPtr length = (IntPtr)(void*)(uint)span.Length;
173203

174204
return SpanHelper.Count(ref r0, length, value);
175205
}
@@ -291,11 +321,11 @@ public static ReadOnlySpanTokenizer<T> Tokenize<T>(this ReadOnlySpan<T> span, T
291321
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
292322
[Pure]
293323
[MethodImpl(MethodImplOptions.AggressiveInlining)]
294-
public static int GetDjb2HashCode<T>(this ReadOnlySpan<T> span)
324+
public static unsafe int GetDjb2HashCode<T>(this ReadOnlySpan<T> span)
295325
where T : notnull
296326
{
297327
ref T r0 = ref MemoryMarshal.GetReference(span);
298-
IntPtr length = (IntPtr)span.Length;
328+
IntPtr length = (IntPtr)(void*)(uint)span.Length;
299329

300330
return SpanHelper.GetDjb2HashCode(ref r0, length);
301331
}

Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ public static ref T DangerousGetReference<T>(this Span<T> span)
4040
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
4141
[Pure]
4242
[MethodImpl(MethodImplOptions.AggressiveInlining)]
43-
public static ref T DangerousGetReferenceAt<T>(this Span<T> span, int i)
43+
public static unsafe ref T DangerousGetReferenceAt<T>(this Span<T> span, int i)
4444
{
4545
ref T r0 = ref MemoryMarshal.GetReference(span);
46-
ref T ri = ref Unsafe.Add(ref r0, i);
46+
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
4747

4848
return ref ri;
4949
}
@@ -140,11 +140,11 @@ public static unsafe int IndexOf<T>(this Span<T> span, ref T value)
140140
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
141141
[Pure]
142142
[MethodImpl(MethodImplOptions.AggressiveInlining)]
143-
public static int Count<T>(this Span<T> span, T value)
143+
public static unsafe int Count<T>(this Span<T> span, T value)
144144
where T : IEquatable<T>
145145
{
146146
ref T r0 = ref MemoryMarshal.GetReference(span);
147-
IntPtr length = (IntPtr)span.Length;
147+
IntPtr length = (IntPtr)(void*)(uint)span.Length;
148148

149149
return SpanHelper.Count(ref r0, length, value);
150150
}
@@ -211,11 +211,11 @@ public static SpanTokenizer<T> Tokenize<T>(this Span<T> span, T separator)
211211
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
212212
[Pure]
213213
[MethodImpl(MethodImplOptions.AggressiveInlining)]
214-
public static int GetDjb2HashCode<T>(this Span<T> span)
214+
public static unsafe int GetDjb2HashCode<T>(this Span<T> span)
215215
where T : notnull
216216
{
217217
ref T r0 = ref MemoryMarshal.GetReference(span);
218-
IntPtr length = (IntPtr)span.Length;
218+
IntPtr length = (IntPtr)(void*)(uint)span.Length;
219219

220220
return SpanHelper.GetDjb2HashCode(ref r0, length);
221221
}

Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public static ref char DangerousGetReference(this string text)
4848
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
4949
[Pure]
5050
[MethodImpl(MethodImplOptions.AggressiveInlining)]
51-
public static ref char DangerousGetReferenceAt(this string text, int i)
51+
public static unsafe ref char DangerousGetReferenceAt(this string text, int i)
5252
{
5353
#if NETCOREAPP3_1
5454
ref char r0 = ref Unsafe.AsRef(text.GetPinnableReference());
@@ -57,7 +57,7 @@ public static ref char DangerousGetReferenceAt(this string text, int i)
5757
#else
5858
ref char r0 = ref MemoryMarshal.GetReference(text.AsSpan());
5959
#endif
60-
ref char ri = ref Unsafe.Add(ref r0, i);
60+
ref char ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
6161

6262
return ref ri;
6363
}
@@ -91,10 +91,10 @@ private sealed class RawStringData
9191
/// <returns>The number of occurrences of <paramref name="c"/> in <paramref name="text"/>.</returns>
9292
[Pure]
9393
[MethodImpl(MethodImplOptions.AggressiveInlining)]
94-
public static int Count(this string text, char c)
94+
public static unsafe int Count(this string text, char c)
9595
{
9696
ref char r0 = ref text.DangerousGetReference();
97-
IntPtr length = (IntPtr)text.Length;
97+
IntPtr length = (IntPtr)(void*)(uint)text.Length;
9898

9999
return SpanHelper.Count(ref r0, length, c);
100100
}
@@ -157,10 +157,10 @@ public static ReadOnlySpanTokenizer<char> Tokenize(this string text, char separa
157157
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
158158
[Pure]
159159
[MethodImpl(MethodImplOptions.AggressiveInlining)]
160-
public static int GetDjb2HashCode(this string text)
160+
public static unsafe int GetDjb2HashCode(this string text)
161161
{
162162
ref char r0 = ref text.DangerousGetReference();
163-
IntPtr length = (IntPtr)text.Length;
163+
IntPtr length = (IntPtr)(void*)(uint)text.Length;
164164

165165
return SpanHelper.GetDjb2HashCode(ref r0, length);
166166
}

Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public static int Combine(ReadOnlySpan<T> span)
5757
/// <remarks>The returned hash code is not processed through <see cref="HashCode"/> APIs.</remarks>
5858
[Pure]
5959
[MethodImpl(MethodImplOptions.AggressiveInlining)]
60-
internal static int CombineValues(ReadOnlySpan<T> span)
60+
internal static unsafe int CombineValues(ReadOnlySpan<T> span)
6161
{
6262
ref T r0 = ref MemoryMarshal.GetReference(span);
6363

@@ -67,13 +67,19 @@ internal static int CombineValues(ReadOnlySpan<T> span)
6767
// compiler, so this branch will never actually be executed by the code.
6868
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
6969
{
70-
return SpanHelper.GetDjb2HashCode(ref r0, (IntPtr)span.Length);
70+
return SpanHelper.GetDjb2HashCode(ref r0, (IntPtr)(void*)(uint)span.Length);
7171
}
7272
#endif
7373

74-
// Get the info for the target memory area to process
74+
// Get the info for the target memory area to process.
75+
// The line below is computing the total byte size for the span,
76+
// and we cast both input factors to uint first to avoid sign extensions
77+
// (they're both guaranteed to always be positive values), and to let the
78+
// JIT avoid the 64 bit computation entirely when running in a 32 bit
79+
// process. In that case it will just compute the byte size as a 32 bit
80+
// multiplication with overflow, which is guaranteed never to happen anyway.
7581
ref byte rb = ref Unsafe.As<T, byte>(ref r0);
76-
IntPtr length = (IntPtr)((long)span.Length * Unsafe.SizeOf<T>());
82+
IntPtr length = (IntPtr)(void*)((uint)span.Length * (uint)Unsafe.SizeOf<T>());
7783

7884
return SpanHelper.GetDjb2LikeByteHash(ref rb, length);
7985
}

Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IInAction.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public InActionInvoker(
136136
/// </summary>
137137
/// <param name="i">The index of the batch to process</param>
138138
[MethodImpl(MethodImplOptions.AggressiveInlining)]
139-
public void Invoke(int i)
139+
public unsafe void Invoke(int i)
140140
{
141141
int
142142
low = i * this.batchSize,
@@ -147,7 +147,7 @@ public void Invoke(int i)
147147

148148
for (int j = low; j < end; j++)
149149
{
150-
ref TItem rj = ref Unsafe.Add(ref r0, j);
150+
ref TItem rj = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)j);
151151

152152
Unsafe.AsRef(this.action).Invoke(rj);
153153
}

Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ForEach.IRefAction.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public RefActionInvoker(
136136
/// </summary>
137137
/// <param name="i">The index of the batch to process</param>
138138
[MethodImpl(MethodImplOptions.AggressiveInlining)]
139-
public void Invoke(int i)
139+
public unsafe void Invoke(int i)
140140
{
141141
int
142142
low = i * this.batchSize,
@@ -147,7 +147,7 @@ public void Invoke(int i)
147147

148148
for (int j = low; j < end; j++)
149149
{
150-
ref TItem rj = ref Unsafe.Add(ref r0, j);
150+
ref TItem rj = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)j);
151151

152152
Unsafe.AsRef(this.action).Invoke(ref rj);
153153
}

Microsoft.Toolkit.HighPerformance/Microsoft.Toolkit.HighPerformance.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- MemoryBufferWriter&lt;T&gt;: an IBufferWriter&lt;T&gt;: implementation that can wrap external Memory&lt;T&gt;: instances.
1313
- MemoryOwner&lt;T&gt;: an IMemoryOwner&lt;T&gt; implementation with an embedded length and a fast Span&lt;T&gt; accessor.
1414
- SpanOwner&lt;T&gt;: a stack-only type with the ability to rent a buffer of a specified length and getting a Span&lt;T&gt; from it.
15+
- StringPool: a configurable pool for string instances that be used to minimize allocations when creating multiple strings from char buffers.
1516
- String, array, Span&lt;T&gt;, Memory&lt;T&gt; extensions and more, all focused on high performance.
1617
- HashCode&lt;T&gt;: a SIMD-enabled extension of HashCode to quickly process sequences of values.
1718
- BitHelper: a class with helper methods to perform bit operations on numeric types.

0 commit comments

Comments
 (0)