Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RentFixed extension #23

Merged
merged 17 commits into from
Jan 15, 2025
39 changes: 36 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,12 @@ interaction between .NET and native P/Invoke methods.

## Features

- **UTF-8/ASCII String Handling**: Seamlessly work with UTF-8 encoded strings in interop contexts.
- **Managed Buffers**: Dynamically allocate object references on the stack with minimal effort.
- **Safe Memory Manipulation**: Eliminate direct pointer manipulation and unsafe code requirements.
- **UTF-8/ASCII String Handling**: Seamlessly work with UTF-8 encoded strings in interop
contexts. [More info](src/Intermediate/Rxmxnx.PInvoke.CString.Intermediate/README.md)
- **Managed Buffers**: Dynamically allocate object references on the stack with minimal
effort. [More info](src/Intermediate/Rxmxnx.PInvoke.Buffers.Intermediate/README.md)
- **Safe Memory Manipulation**: Eliminate direct pointer manipulation and unsafe code
requirements. [More info](src/Intermediate/Rxmxnx.PInvoke.Extensions.Intermediate/README.md)

---

Expand Down Expand Up @@ -2486,6 +2489,18 @@ Set of extensions for basic operations with `String` instances.

Set of extensions for basic operations with `unmanaged` values.

- <details>
<summary>RentFixed&lt;T&gt;(this ArrayPool&lt;T&gt;, Int32, Boolean)</summary>

Rents and pins an array of minimum number of `T` elements from given array pool,
ensuring a safe context for accessing the fixed memory.
</details>
- <details>
<summary>RentFixed&lt;T&gt;(this ArrayPool&lt;T&gt;, Int32, Boolean, out Int32)</summary>

Rents and pins an array of minimum number of `T` elements from given array pool,
ensuring a safe context for accessing the fixed memory.
</details>
- <details>
<summary>ToBytes&lt;T&gt;(this T)</summary>

Expand Down Expand Up @@ -2626,6 +2641,24 @@ This class allows to allocate buffers on stack if possible.

**Note:** `T` is `struct`. `TBuffer` is `struct` and `IManagedBuffer<T?>`.
</details>
- <details>
<summary>PrepareBinaryBuffer(UInt16)</summary>
Prepares the binary buffer metadata needed to allocate given number of objects.
</details>
- <details>
<summary>PrepareBinaryBuffer&lt;T&gt;(UInt16)</summary>

Prepares the binary buffer metadata needed to allocate given number of `T` items.

**Note:** `T` is `struct`.
</details>
- <details>
<summary>PrepareBinaryBufferNullable&lt;T&gt;()</summary>

Prepares the binary buffer metadata needed to allocate given number of `T?` items.

**Note:** `T` is `struct`.
</details>

</details>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ public static partial class BufferManager
public static Boolean BufferAutoCompositionEnabled
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => !AotInfo.IsReflectionDisabled &&
(!AppContext.TryGetSwitch("PInvoke.DisableBufferAutoComposition", out Boolean disable) || !disable);
get
=> !AotInfo.IsReflectionDisabled &&
(!AppContext.TryGetSwitch("PInvoke.DisableBufferAutoComposition", out Boolean disable) || !disable);
}

/// <summary>
Expand Down Expand Up @@ -106,13 +107,55 @@ public static void Register<TBuffer>() where TBuffer : struct, IManagedBuffer<Ob
/// <typeparam name="TBuffer">Type of <typeparamref name="T"/> buffer.</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Register<T, TBuffer>() where TBuffer : struct, IManagedBuffer<T> where T : struct
=> MetadataManager<T>.RegisterBuffer<TBuffer>();
{
// If unmanaged type, stackalloc should be used.
if (!RuntimeHelpers.IsReferenceOrContainsReferences<T>()) return;
MetadataManager<T>.RegisterBuffer<TBuffer>();
}
/// <summary>
/// Registers <typeparamref name="T"/> buffer.
/// </summary>
/// <typeparam name="T">Type of nullable items in the buffer.</typeparam>
/// <typeparam name="TBuffer">Type of <see name="Nullable{T}"/> buffer.</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterNullable<T, TBuffer>() where TBuffer : struct, IManagedBuffer<T?> where T : struct
=> MetadataManager<T?>.RegisterBuffer<TBuffer>();
{
// If unmanaged type, stackalloc should be used.
if (!RuntimeHelpers.IsReferenceOrContainsReferences<T>()) return;
MetadataManager<T?>.RegisterBuffer<TBuffer>();
}
/// <summary>
/// Prepares the binary buffer metadata needed to allocate <paramref name="count"/> objects.
/// </summary>
/// <param name="count">Amount of items in required buffer.</param>
/// <exception cref="InvalidOperationException">Throw if missing metadata for any buffer component.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void PrepareBinaryBuffer(UInt16 count) => MetadataManager<Object>.PrepareBinaryMetadata(count);
/// <summary>
/// Prepares the binary buffer metadata needed to allocate <paramref name="count"/> <typeparamref name="T"/> items.
/// </summary>
/// <typeparam name="T">Type of items in the buffer.</typeparam>
/// <param name="count">Amount of items in required buffer.</param>
/// <exception cref="InvalidOperationException">Throw if missing metadata for any buffer component.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void PrepareBinaryBuffer<T>(UInt16 count) where T : struct
{
// If unmanaged type, stackalloc should be used.
if (!RuntimeHelpers.IsReferenceOrContainsReferences<T>()) return;
MetadataManager<T>.PrepareBinaryMetadata(count);
}
/// <summary>
/// Prepares the binary buffer metadata needed to allocate <paramref name="count"/> nullable <typeparamref name="T"/>
/// items.
/// </summary>
/// <typeparam name="T">Type of nullable items in the buffer.</typeparam>
/// <param name="count">Amount of items in required buffer.</param>
/// <exception cref="InvalidOperationException">Throw if missing metadata for any buffer component.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void PrepareBinaryBufferNullable<T>(UInt16 count) where T : struct
{
// If unmanaged type, stackalloc should be used.
if (!RuntimeHelpers.IsReferenceOrContainsReferences<T>()) return;
MetadataManager<T?>.PrepareBinaryMetadata(count);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,30 @@ internal static partial class MetadataManager<T>
return MetadataManager<T>.GetBinaryMetadata(count, true);
}
/// <summary>
/// Prepares internal metadata cache for allocations of <paramref name="count"/> items.
/// </summary>
/// <param name="count">Amount of items in required buffer.</param>
/// <exception cref="InvalidOperationException">Throw if missing metadata for any buffer component.</exception>
public static void PrepareBinaryMetadata(UInt16 count)
{
Type typeofT = typeof(T);
BufferTypeMetadata<T>? metadata = default;
foreach (UInt16 comp in BufferManager.GetBinaryComponents(count))
{
BufferTypeMetadata<T>? compMetadata = MetadataManager<T>.GetFundamental(comp);
ValidationUtilities.ThrowIfNullMetadata(typeofT, comp, compMetadata is null);
if (metadata is null)
{
metadata = compMetadata;
continue;
}

UInt16 composeSize = (UInt16)(comp + metadata.Size);
metadata = MetadataManager<T>.GetBinaryMetadata(composeSize, false);
ValidationUtilities.ThrowIfNullMetadata(typeofT, composeSize, metadata is null);
}
}
/// <summary>
/// Creates <see cref="BufferTypeMetadata{T}"/> for <see cref="Composite{TBufferA,TBufferB,T}"/>.
/// </summary>
/// <param name="typeofA">The type of low buffer.</param>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace Rxmxnx.PInvoke;

[SuppressMessage(SuppressMessageConstants.CSharpSquid, SuppressMessageConstants.CheckIdS3011)]
[SuppressMessage(SuppressMessageConstants.CSharpSquid, SuppressMessageConstants.CheckIdS6640)]
public static partial class BufferManager
{
/// <summary>
Expand All @@ -18,7 +19,6 @@ public static partial class BufferManager
/// Type of <see cref="Composite{TBufferA,TBufferB,T}"/>.
/// </summary>
private static readonly Type typeofComposite = typeof(Composite<,,>);

/// <summary>
/// Allocates a heap buffer of size of <paramref name="count"/> elements.
/// </summary>
Expand Down Expand Up @@ -230,6 +230,12 @@ private static TResult AllocObject<T, TState, TResult>(UInt16 count, TState stat
/// </param>
private static void AllocValue<T>(UInt16 count, ScopedBufferAction<T> action, Boolean isMinimumCount)
{
if (!RuntimeHelpers.IsReferenceOrContainsReferences<T>())
{
BufferManager.AllocStack(count, action);
return;
}

BufferTypeMetadata<T>? metadata = MetadataManager<T>.GetMetadata(count);
if (metadata is null || (!isMinimumCount && metadata.Size != count))
{
Expand Down Expand Up @@ -258,6 +264,12 @@ private static void AllocValue<T, TState>(UInt16 count, TState state, ScopedBuff
where TState : allows ref struct
#endif
{
if (!RuntimeHelpers.IsReferenceOrContainsReferences<T>())
{
BufferManager.AllocStack(count, state, action);
return;
}

BufferTypeMetadata<T>? metadata = MetadataManager<T>.GetMetadata(count);
if (metadata is null || (!isMinimumCount && metadata.Size != count))
{
Expand All @@ -283,6 +295,9 @@ private static void AllocValue<T, TState>(UInt16 count, TState state, ScopedBuff
private static TResult AllocValue<T, TResult>(UInt16 count, ScopedBufferFunc<T, TResult> func,
Boolean isMinimumCount)
{
if (!RuntimeHelpers.IsReferenceOrContainsReferences<T>())
return BufferManager.AllocStack(count, func);

BufferTypeMetadata<T>? metadata = MetadataManager<T>.GetMetadata(count);
if (metadata is null || (!isMinimumCount && metadata.Size != count))
return BufferManager.AllocHeap(count, func);
Expand Down Expand Up @@ -310,6 +325,9 @@ private static TResult AllocValue<T, TState, TResult>(UInt16 count, TState state
where TState : allows ref struct
#endif
{
if (!RuntimeHelpers.IsReferenceOrContainsReferences<T>())
return BufferManager.AllocStack(count, state, func);

BufferTypeMetadata<T>? metadata = MetadataManager<T>.GetMetadata(count);
if (metadata is null || (!isMinimumCount && metadata.Size != count))
return BufferManager.AllocHeap(count, state, func);
Expand All @@ -324,4 +342,98 @@ private static TResult AllocValue<T, TState, TResult>(UInt16 count, TState state
/// <param name="space">Maximum binary power in the binary space.</param>
/// <returns>The maximum value in the given binary space.</returns>
private static UInt16 GetMaxValue(UInt16 space) => (UInt16)(space * 2 - 1);
/// <summary>
/// Retrieves the components sizes for given <paramref name="count"/>.
/// </summary>
/// <param name="count">Amount of items in required buffer.</param>
/// <returns>Enumeration of components sizes.</returns>
private static IEnumerable<UInt16> GetBinaryComponents(UInt16 count)
{
for (UInt16 i = 0; i < 16; i++)
{
UInt16 mask = (UInt16)(1 << i);
if ((count & mask) != 0) yield return mask;
}
}
/// <summary>
/// Allocates a stack buffer of size of <paramref name="count"/> elements.
/// </summary>
/// <typeparam name="T">The type of items in the buffer.</typeparam>
/// <param name="count">Required buffer size.</param>
/// <param name="action">Method to execute.</param>
#pragma warning disable CS8500
[ExcludeFromCodeCoverage]
private static unsafe void AllocStack<T>(UInt16 count, ScopedBufferAction<T> action)
{
Int32 sizeOfT = sizeof(T);
Span<Byte> bytes = stackalloc Byte[count * sizeOfT];
ref T refT = ref Unsafe.As<Byte, T>(ref MemoryMarshal.GetReference(bytes));
Span<T> span = MemoryMarshal.CreateSpan(ref refT, count);
ScopedBuffer<T> buffer = new(span, false, span.Length);
action(buffer);
}
/// <summary>
/// Allocates a stack buffer of size of <paramref name="count"/> elements.
/// </summary>
/// <typeparam name="T">The type of items in the buffer.</typeparam>
/// <typeparam name="TState">Type of state object.</typeparam>
/// <param name="count">Required buffer size.</param>
/// <param name="state">State object.</param>
/// <param name="action">Method to execute.</param>
[ExcludeFromCodeCoverage]
private static unsafe void AllocStack<T, TState>(UInt16 count, TState state, ScopedBufferAction<T, TState> action)
#if NET9_0_OR_GREATER
where TState : allows ref struct
#endif
{
Int32 sizeOfT = sizeof(T);
Span<Byte> bytes = stackalloc Byte[count * sizeOfT];
ref T refT = ref Unsafe.As<Byte, T>(ref MemoryMarshal.GetReference(bytes));
Span<T> span = MemoryMarshal.CreateSpan(ref refT, count);
ScopedBuffer<T> buffer = new(span, false, span.Length);
action(buffer, state);
}
/// <summary>
/// Allocates a stack buffer of size of <paramref name="count"/> elements.
/// </summary>
/// <typeparam name="T">The type of items in the buffer.</typeparam>
/// <typeparam name="TResult">Type of <paramref name="func"/> result.</typeparam>
/// <param name="count">Required buffer size.</param>
/// <param name="func">Function to execute.</param>
/// <returns><paramref name="func"/> result.</returns>
[ExcludeFromCodeCoverage]
private static unsafe TResult AllocStack<T, TResult>(UInt16 count, ScopedBufferFunc<T, TResult> func)
{
Int32 sizeOfT = sizeof(T);
Span<Byte> bytes = stackalloc Byte[count * sizeOfT];
ref T refT = ref Unsafe.As<Byte, T>(ref MemoryMarshal.GetReference(bytes));
Span<T> span = MemoryMarshal.CreateSpan(ref refT, count);
ScopedBuffer<T> buffer = new(span, false, span.Length);
return func(buffer);
}
/// <summary>
/// Allocates a stack buffer of size of <paramref name="count"/> elements.
/// </summary>
/// <typeparam name="T">The type of items in the buffer.</typeparam>
/// <typeparam name="TState">Type of state object.</typeparam>
/// <typeparam name="TResult">Type of <paramref name="func"/> result.</typeparam>
/// <param name="count">Required buffer size.</param>
/// <param name="state">State object.</param>
/// <param name="func">Function to execute.</param>
/// <returns><paramref name="func"/> result.</returns>
[ExcludeFromCodeCoverage]
private static unsafe TResult AllocStack<T, TState, TResult>(UInt16 count, TState state,
ScopedBufferFunc<T, TState, TResult> func)
#if NET9_0_OR_GREATER
where TState : allows ref struct
#endif
{
Int32 sizeOfT = sizeof(T);
Span<Byte> bytes = stackalloc Byte[count * sizeOfT];
ref T refT = ref Unsafe.As<Byte, T>(ref MemoryMarshal.GetReference(bytes));
Span<T> span = MemoryMarshal.CreateSpan(ref refT, count);
ScopedBuffer<T> buffer = new(span, false, span.Length);
return func(buffer, state);
}
#pragma warning restore CS8500
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ private readonly struct TransformationState<T>(ScopedBufferAction<T> action)
/// <param name="state">State object.</param>
public static void Execute(ScopedBuffer<Object> buffer, TransformationState<T> state)
{
Span<T> span =
MemoryMarshal.CreateSpan(ref Unsafe.As<Object, T>(ref MemoryMarshal.GetReference(buffer.Span)),
buffer.Span.Length);
ref Object refObject = ref MemoryMarshal.GetReference(buffer.Span);
ref T refT = ref Unsafe.As<Object, T>(ref refObject);
Span<T> span = MemoryMarshal.CreateSpan(ref refT, buffer.Span.Length);
ScopedBuffer<T> bufferT = new(span, !buffer.InStack, buffer.FullLength, buffer.BufferMetadata);
state._action(bufferT);
}
Expand All @@ -49,9 +49,9 @@ private readonly struct TransformationState<T, TResult>(ScopedBufferFunc<T, TRes
/// <param name="state">State object.</param>
public static TResult Execute(ScopedBuffer<Object> buffer, TransformationState<T, TResult> state)
{
Span<T> span =
MemoryMarshal.CreateSpan(ref Unsafe.As<Object, T>(ref MemoryMarshal.GetReference(buffer.Span)),
buffer.Span.Length);
ref Object refObject = ref MemoryMarshal.GetReference(buffer.Span);
ref T refT = ref Unsafe.As<Object, T>(ref refObject);
Span<T> span = MemoryMarshal.CreateSpan(ref refT, buffer.Span.Length);
ScopedBuffer<T> bufferT = new(span, !buffer.InStack, buffer.FullLength, buffer.BufferMetadata);
return state._func(bufferT);
}
Expand Down
24 changes: 19 additions & 5 deletions src/Intermediate/Rxmxnx.PInvoke.Buffers.Intermediate/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
`Rxmxnx.PInvoke.Extensions` supports the use of two types of buffers: binary and non-binary. The maximum capacity of any
buffer is limited to 2<sup>15</sup> elements. However, this maximum capacity may not always be allocatable at runtime.

Internally, all reference types utilize buffers of type `Object`. Only value types (both managed and unmanaged) require
the use of buffers specific to their type.
Internally, all reference types utilize buffers of type `Object`. Only not unmanaged value types require
the use of buffers specific to their type, the unmanaged ones uses stackalloc.

---

Expand All @@ -28,9 +28,11 @@ In a Native AOT runtime, binary buffer composition requires metadata preservatio
Below is an example of the metadata preservation needed to compose a binary buffer with a capacity of 10 elements of any
reference type Composite(2<sup>1</sup>, 2<sup>3</sup>, `Object`).

**Notes**:
* 2<sup>3</sup> is Composite(2<sup>2</sup>, 2<sup>2</sup>, `Object`), 2<sup>2</sup> is Composite(2<sup>1</sup>, 2<sup>1</sup>, `Object`), 2<sup>1</sup> is
Composite(2<sup>0</sup>, 2<sup>0</sup>, `Object`) and 2<sup>0</sup> is Atomic(`Object`).
**Notes**:

* 2<sup>3</sup> is Composite(2<sup>2</sup>, 2<sup>2</sup>, `Object`), 2<sup>2</sup> is Composite(2<sup>1</sup>, 2<sup>
1</sup>, `Object`), 2<sup>1</sup> is
Composite(2<sup>0</sup>, 2<sup>0</sup>, `Object`) and 2<sup>0</sup> is Atomic(`Object`).
* Once a buffer is composed, it becomes available for use. This process is executed only once for each capacity.

```xml
Expand Down Expand Up @@ -71,6 +73,14 @@ Composite(2<sup>0</sup>, 2<sup>0</sup>, `Object`) and 2<sup>0</sup> is Atomic(`O
</Directives>
```

### Preparation

Binary buffers can be statically prepared to allocate a specific number of elements using auto-composition to cache the
buffer metadata with the required size.

**Note:** It is ideal for scenarios where buffer allocation needs to occur as transparently as possible, without
requiring additional allocations.

---

## Non-Binary Buffers
Expand All @@ -90,3 +100,7 @@ There are three buffer registration options:
1. For `Object` type.
2. For generic `struct` type.
3. For generic nullable `struct` type.

## Binary buffer Preparation

Binary buffers can be statically prepared for a given count number elements. Thise
Loading
Loading