Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6c7ffe7
Added initial version of Memory.Cast
Sergio0694 Oct 4, 2020
87c16a8
Code refactoring
Sergio0694 Oct 4, 2020
ccab26b
Added the extension for ReadOnlyMemory<T>
Sergio0694 Oct 4, 2020
caca3b1
Added missing file headers
Sergio0694 Oct 4, 2020
77f203f
Added validation for Pin methods
Sergio0694 Oct 4, 2020
79665cd
Fixed type constraints for [ReadOnly]Span<T>.Cast
Sergio0694 Oct 4, 2020
db31e98
Added [ReadOnly]Memory<T>.AsBytes extensions
Sergio0694 Oct 4, 2020
ba22d22
Optimized [ReadOnly]Memory<T> cast to source T
Sergio0694 Oct 4, 2020
53cb75f
Fixed offset/length computation with double cast
Sergio0694 Oct 4, 2020
b904790
Added some unit tests
Sergio0694 Oct 4, 2020
ba514b0
Added unit test with types != byte
Sergio0694 Oct 4, 2020
c523742
Added unit tests for slices and MemoryManager<T>
Sergio0694 Oct 4, 2020
ae2844a
Bug fixed in Pin methods, added unit tests
Sergio0694 Oct 4, 2020
62d1a2a
Added support for casting Memory<char> from string
Sergio0694 Oct 5, 2020
46c0273
Merge branch 'master' into feature/memory-cast
Sergio0694 Oct 5, 2020
aa895a4
Added unit tests for StringMemoryManager<TTo>
Sergio0694 Oct 5, 2020
be5312d
Merge branch 'master' into feature/memory-cast
Sergio0694 Oct 6, 2020
7184fe2
Added comments to unit tests
Sergio0694 Oct 30, 2020
12ae446
Merge branch 'master' into feature/memory-cast
Sergio0694 Nov 5, 2020
cad806c
Fixed an issue with an XML comment
Sergio0694 Nov 6, 2020
259418b
Minor codegen improvements
Sergio0694 Nov 6, 2020
5aa217f
Merge branch 'master' into feature/memory-cast
Rosuavio Nov 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
using Microsoft.Toolkit.HighPerformance.Extensions;
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;

namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
{
/// <summary>
/// A custom <see cref="MemoryManager{T}"/> that casts data from a <typeparamref name="TFrom"/> array, to <typeparamref name="TTo"/> values.
/// </summary>
/// <typeparam name="TFrom">The source type of items to read.</typeparam>
/// <typeparam name="TTo">The target type to cast the source items to.</typeparam>
internal sealed class ArrayMemoryManager<TFrom, TTo> : MemoryManager<TTo>, IMemoryManager
where TFrom : unmanaged
where TTo : unmanaged
{
/// <summary>
/// The source <typeparamref name="TFrom"/> array to read data from.
/// </summary>
private readonly TFrom[] array;

/// <summary>
/// The starting offset within <see name="array"/>.
/// </summary>
private readonly int offset;

/// <summary>
/// The original used length for <see name="array"/>.
/// </summary>
private readonly int length;

/// <summary>
/// Initializes a new instance of the <see cref="ArrayMemoryManager{TFrom, TTo}"/> class.
/// </summary>
/// <param name="array">The source <typeparamref name="TFrom"/> array to read data from.</param>
/// <param name="offset">The starting offset within <paramref name="array"/>.</param>
/// <param name="length">The original used length for <paramref name="array"/>.</param>
public ArrayMemoryManager(TFrom[] array, int offset, int length)
{
this.array = array;
this.offset = offset;
this.length = length;
}

/// <inheritdoc/>
public override Span<TTo> GetSpan()
{
#if SPAN_RUNTIME_SUPPORT
ref TFrom r0 = ref this.array.DangerousGetReferenceAt(this.offset);
ref TTo r1 = ref Unsafe.As<TFrom, TTo>(ref r0);
int length = RuntimeHelpers.ConvertLength<TFrom, TTo>(this.length);

return MemoryMarshal.CreateSpan(ref r1, length);
#else
Span<TFrom> span = this.array.AsSpan(this.offset, this.length);

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

/// <inheritdoc/>
public override unsafe MemoryHandle Pin(int elementIndex = 0)
{
if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>()))
{
ThrowArgumentOutOfRangeExceptionForInvalidIndex();
}

int
bytePrefix = this.offset * Unsafe.SizeOf<TFrom>(),
byteSuffix = elementIndex * Unsafe.SizeOf<TTo>(),
byteOffset = bytePrefix + byteSuffix;

GCHandle handle = GCHandle.Alloc(this.array, GCHandleType.Pinned);

ref TFrom r0 = ref this.array.DangerousGetReference();
ref byte r1 = ref Unsafe.As<TFrom, byte>(ref r0);
ref byte r2 = ref Unsafe.Add(ref r1, byteOffset);
void* pi = Unsafe.AsPointer(ref r2);

return new MemoryHandle(pi, handle);
}

/// <inheritdoc/>
public override void Unpin()
{
}

/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}

/// <inheritdoc/>
public Memory<T> GetMemory<T>(int offset, int length)
where T : unmanaged
{
// We need to calculate the right offset and length of the new Memory<T>. The local offset
// is the original offset into the wrapped TFrom[] array, while the input offset is the one
// with respect to TTo items in the Memory<TTo> instance that is currently being cast.
int
absoluteOffset = this.offset + RuntimeHelpers.ConvertLength<TTo, TFrom>(offset),
absoluteLength = RuntimeHelpers.ConvertLength<TTo, TFrom>(length);

// We have a special handling in cases where the user is circling back to the original type
// of the wrapped array. In this case we can just return a memory wrapping that array directly,
// with offset and length being adjusted, without the memory manager indirection.
if (typeof(T) == typeof(TFrom))
{
return (Memory<T>)(object)this.array.AsMemory(absoluteOffset, absoluteLength);
}

return new ArrayMemoryManager<TFrom, T>(this.array, absoluteOffset, absoluteLength).Memory;
}

/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the target index for <see cref="Pin"/> is invalid.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForInvalidIndex()
{
throw new ArgumentOutOfRangeException("elementIndex", "The input index is not in the valid range");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Buffers;

namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces
{
/// <summary>
/// An interface for a <see cref="MemoryManager{T}"/> instance that can reinterpret its underlying data.
/// </summary>
internal interface IMemoryManager
{
/// <summary>
/// Creates a new <see cref="Memory{T}"/> that reinterprets the underlying data for the current instance.
/// </summary>
/// <typeparam name="T">The target type to cast the items to.</typeparam>
/// <param name="offset">The starting offset within the data store.</param>
/// <param name="length">The original used length for the data store.</param>
/// <returns>A new <see cref="Memory{T}"/> instance of the specified type, reinterpreting the current items.</returns>
Memory<T> GetMemory<T>(int offset, int length)
where T : unmanaged;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;

namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
{
/// <summary>
/// A custom <see cref="MemoryManager{T}"/> that casts data from a <see cref="MemoryManager{T}"/> of <typeparamref name="TFrom"/>, to <typeparamref name="TTo"/> values.
/// </summary>
/// <typeparam name="TFrom">The source type of items to read.</typeparam>
/// <typeparam name="TTo">The target type to cast the source items to.</typeparam>
internal sealed class ProxyMemoryManager<TFrom, TTo> : MemoryManager<TTo>, IMemoryManager
where TFrom : unmanaged
where TTo : unmanaged
{
/// <summary>
/// The source <see cref="MemoryManager{T}"/> to read data from.
/// </summary>
private readonly MemoryManager<TFrom> memoryManager;

/// <summary>
/// The starting offset within <see name="memoryManager"/>.
/// </summary>
private readonly int offset;

/// <summary>
/// The original used length for <see name="memoryManager"/>.
/// </summary>
private readonly int length;

/// <summary>
/// Initializes a new instance of the <see cref="ProxyMemoryManager{TFrom, TTo}"/> class.
/// </summary>
/// <param name="memoryManager">The source <see cref="MemoryManager{T}"/> to read data from.</param>
/// <param name="offset">The starting offset within <paramref name="memoryManager"/>.</param>
/// <param name="length">The original used length for <paramref name="memoryManager"/>.</param>
public ProxyMemoryManager(MemoryManager<TFrom> memoryManager, int offset, int length)
{
this.memoryManager = memoryManager;
this.offset = offset;
this.length = length;
}

/// <inheritdoc/>
public override Span<TTo> GetSpan()
{
Span<TFrom> span = this.memoryManager.GetSpan().Slice(this.offset, this.length);

return MemoryMarshal.Cast<TFrom, TTo>(span);
}

/// <inheritdoc/>
public override MemoryHandle Pin(int elementIndex = 0)
{
if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>()))
{
ThrowArgumentExceptionForInvalidIndex();
}

int
bytePrefix = this.offset * Unsafe.SizeOf<TFrom>(),
byteSuffix = elementIndex * Unsafe.SizeOf<TTo>(),
byteOffset = bytePrefix + byteSuffix;

#if NETSTANDARD1_4
int
shiftedOffset = byteOffset / Unsafe.SizeOf<TFrom>(),
remainder = byteOffset - (shiftedOffset * Unsafe.SizeOf<TFrom>());
#else
int shiftedOffset = Math.DivRem(byteOffset, Unsafe.SizeOf<TFrom>(), out int remainder);
#endif

if (remainder != 0)
{
ThrowArgumentExceptionForInvalidAlignment();
}

return this.memoryManager.Pin(shiftedOffset);
}

/// <inheritdoc/>
public override void Unpin()
{
this.memoryManager.Unpin();
}

/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
((IDisposable)this.memoryManager).Dispose();
}

/// <inheritdoc/>
public Memory<T> GetMemory<T>(int offset, int length)
where T : unmanaged
{
// Like in the other memory manager, calculate the absolute offset and length
int
absoluteOffset = this.offset + RuntimeHelpers.ConvertLength<TTo, TFrom>(offset),
absoluteLength = RuntimeHelpers.ConvertLength<TTo, TFrom>(length);

// Skip one indirection level and slice the original memory manager, if possible
if (typeof(T) == typeof(TFrom))
{
return (Memory<T>)(object)this.memoryManager.Memory.Slice(absoluteOffset, absoluteLength);
}

return new ProxyMemoryManager<TFrom, T>(this.memoryManager, absoluteOffset, absoluteLength).Memory;
}

/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the target index for <see cref="Pin"/> is invalid.
/// </summary>
private static void ThrowArgumentExceptionForInvalidIndex()
{
throw new ArgumentOutOfRangeException("elementIndex", "The input index is not in the valid range");
}

/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Pin"/> receives an invalid target index.
/// </summary>
private static void ThrowArgumentExceptionForInvalidAlignment()
{
throw new ArgumentOutOfRangeException("elementIndex", "The input index doesn't result in an aligned item access");
}
}
}
Loading