Skip to content

Commit 53cb75f

Browse files
committed
Fixed offset/length computation with double cast
1 parent ba22d22 commit 53cb75f

File tree

4 files changed

+78
-8
lines changed

4 files changed

+78
-8
lines changed

Microsoft.Toolkit.HighPerformance/Buffers/Internals/ArrayMemoryManager{TFrom,TTo}.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Runtime.InteropServices;
99
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
1010
using Microsoft.Toolkit.HighPerformance.Extensions;
11+
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
1112

1213
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
1314
{
@@ -53,16 +54,18 @@ public override Span<TTo> GetSpan()
5354
{
5455
#if SPAN_RUNTIME_SUPPORT
5556
ref TFrom r0 = ref this.array.DangerousGetReferenceAt(this.offset);
57+
ref TTo r1 = ref Unsafe.As<TFrom, TTo>(ref r0);
58+
int length = RuntimeHelpers.ConvertLength<TFrom, TTo>(this.length);
5659

57-
Span<TFrom> span = MemoryMarshal.CreateSpan(ref r0, this.length);
60+
return MemoryMarshal.CreateSpan(ref r1, length);
5861
#else
5962
Span<TFrom> span = this.array.AsSpan(this.offset, this.length);
60-
#endif
6163

6264
// We rely on MemoryMarshal.Cast here to deal with calculating the effective
6365
// size of the new span to return. This will also make the behavior consistent
6466
// for users that are both using this type as well as casting spans directly.
6567
return MemoryMarshal.Cast<TFrom, TTo>(span);
68+
#endif
6669
}
6770

6871
/// <inheritdoc/>
@@ -102,15 +105,22 @@ protected override void Dispose(bool disposing)
102105
public Memory<T> GetMemory<T>(int offset, int length)
103106
where T : unmanaged
104107
{
108+
// We need to calculate the right offset and length of the new Memory<T>. The local offset
109+
// is the original offset into the wrapped TFrom[] array, while the input offset is the one
110+
// with respect to TTo items in the Memory<TTo> instance that is currently being cast.
111+
int
112+
absoluteOffset = this.offset + RuntimeHelpers.ConvertLength<TTo, TFrom>(offset),
113+
absoluteLength = RuntimeHelpers.ConvertLength<TTo, TFrom>(length);
114+
105115
// We have a special handling in cases where the user is circling back to the original type
106116
// of the wrapped array. In this case we can just return a memory wrapping that array directly,
107117
// with offset and length being adjusted, without the memory manager indirection.
108118
if (typeof(T) == typeof(TFrom))
109119
{
110-
return (Memory<T>)(object)this.array.AsMemory(this.offset + offset, length);
120+
return (Memory<T>)(object)this.array.AsMemory(absoluteOffset, absoluteLength);
111121
}
112122

113-
return new ArrayMemoryManager<TFrom, T>(this.array, this.offset + offset, length).Memory;
123+
return new ArrayMemoryManager<TFrom, T>(this.array, absoluteOffset, absoluteLength).Memory;
114124
}
115125

116126
/// <summary>

Microsoft.Toolkit.HighPerformance/Buffers/Internals/ProxyMemoryManager{TFrom,TTo}.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Runtime.CompilerServices;
88
using System.Runtime.InteropServices;
99
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
10+
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
1011

1112
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
1213
{
@@ -97,14 +98,18 @@ protected override void Dispose(bool disposing)
9798
public Memory<T> GetMemory<T>(int offset, int length)
9899
where T : unmanaged
99100
{
100-
// Like in the other memory manager, we can skip one level of indirection in cases
101-
// where the user is just going back to the original memory type, reusing the manager.
101+
// Like in the other memory manager, calculate the absolute offset and length
102+
int
103+
absoluteOffset = this.offset + RuntimeHelpers.ConvertLength<TTo, TFrom>(offset),
104+
absoluteLength = RuntimeHelpers.ConvertLength<TTo, TFrom>(length);
105+
106+
// Skip one indirection level and slice the original memory manager, if possible
102107
if (typeof(T) == typeof(TFrom))
103108
{
104-
return (Memory<T>)(object)this.memoryManager.Memory.Slice(offset, length);
109+
return (Memory<T>)(object)this.memoryManager.Memory.Slice(absoluteOffset, absoluteLength);
105110
}
106111

107-
return new ProxyMemoryManager<TFrom, T>(this.memoryManager, this.offset + offset, length).Memory;
112+
return new ProxyMemoryManager<TFrom, T>(this.memoryManager, absoluteOffset, absoluteLength).Memory;
108113
}
109114

110115
/// <summary>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Runtime.CompilerServices;
1010
using System.Runtime.InteropServices;
1111
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
12+
using RuntimeHelpers = System.Runtime.CompilerServices.RuntimeHelpers;
1213

1314
namespace Microsoft.Toolkit.HighPerformance.Helpers
1415
{
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Diagnostics.Contracts;
7+
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices;
9+
10+
namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
11+
{
12+
/// <summary>
13+
/// A helper class that with utility methods for dealing with references, and other low-level details.
14+
/// </summary>
15+
internal static class RuntimeHelpers
16+
{
17+
/// <summary>
18+
/// Converts a length of items from one size to another. This method exposes the logic from
19+
/// <see cref="MemoryMarshal.Cast{TFrom,TTo}(Span{TFrom})"/>, just for the length conversion.
20+
/// </summary>
21+
/// <typeparam name="TFrom">The source type of items.</typeparam>
22+
/// <typeparam name="TTo">The target type of items.</typeparam>
23+
/// <param name="length">The input length to convert.</param>
24+
/// <returns>The converted length for the specified argument and types.</returns>
25+
[Pure]
26+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
27+
public static int ConvertLength<TFrom, TTo>(int length)
28+
where TFrom : unmanaged
29+
where TTo : unmanaged
30+
{
31+
uint fromSize = (uint)Unsafe.SizeOf<TFrom>();
32+
uint toSize = (uint)Unsafe.SizeOf<TTo>();
33+
uint fromLength = (uint)length;
34+
int toLength;
35+
36+
if (fromSize == toSize)
37+
{
38+
toLength = (int)fromLength;
39+
}
40+
else if (fromSize == 1)
41+
{
42+
toLength = (int)(fromLength / toSize);
43+
}
44+
else
45+
{
46+
ulong toLengthUInt64 = (ulong)fromLength * fromSize / toSize;
47+
48+
toLength = checked((int)toLengthUInt64);
49+
}
50+
51+
return toLength;
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)