Skip to content

Conversation

YoshiRulz
Copy link
Member

@YoshiRulz YoshiRulz commented Sep 26, 2025

namespace BizHawk.Common
{
[InlineArray1(10)]
public readonly partial struct TestROStruct
{
private readonly Guid _element0;
}
[InlineArray1(10)]
public partial struct TestRWStruct
{
private int _element0;
}

generates:

BizHawk.Common.cs
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

#pragma warning disable CS9084 // Struct member returns 'this' or other instance members by reference
namespace BizHawk.Common
{
	file static class Helper
	{
		public static unsafe ReadOnlySpan<TElement> InlineArrayAsReadOnlySpan<TBuffer, TElement>(in TBuffer buffer, int count)
			where TBuffer : struct
			=> new(Unsafe.AsPointer(ref Unsafe.AsRef(in buffer)), count);

		public static unsafe Span<TElement> InlineArrayAsSpan<TBuffer, TElement>(in TBuffer buffer, int count)
			where TBuffer : struct
			=> new(Unsafe.AsPointer(ref Unsafe.AsRef(in buffer)), count);

		public static ref T ThrowAOORE<T>(int index, string message)
			=> throw new ArgumentOutOfRangeException(paramName: nameof(index), index, message: message);
	}

	[StructLayout(LayoutKind.Sequential, Size = ELEM_COUNT * 16)]
	[UnsafeValueType]
	public readonly partial struct TestROStruct
	{
		private const int ELEM_COUNT = 10;

		public static implicit operator /*unchecked*/ ReadOnlySpan<System.Guid>(in TestROStruct buffer)
			=> Helper.InlineArrayAsReadOnlySpan<TestROStruct, System.Guid>(in buffer, ELEM_COUNT);

		public ref readonly System.Guid this[int index]
			=> ref index < 0 || ELEM_COUNT < index
				? ref Helper.ThrowAOORE<System.Guid>(index, "invalid index, must be in 0..<10")
				: ref Unsafe.Add(ref Unsafe.AsRef(in GetPinnableReference()), index);

		public unsafe ref readonly System.Guid GetPinnableReference()
			=> ref _element0;
	}

	[StructLayout(LayoutKind.Sequential, Size = ELEM_COUNT * sizeof(int))]
	[UnsafeValueType]
	public partial struct TestRWStruct
	{
		private const int ELEM_COUNT = 10;

		public static implicit operator /*unchecked*/ ReadOnlySpan<int>(in TestRWStruct buffer)
			=> Helper.InlineArrayAsReadOnlySpan<TestRWStruct, int>(in buffer, ELEM_COUNT);

		public static implicit operator /*unchecked*/ Span<int>(in TestRWStruct buffer)
			=> Helper.InlineArrayAsSpan<TestRWStruct, int>(in buffer, ELEM_COUNT);

		public ref int this[int index]
			=> ref index < 0 || ELEM_COUNT < index
				? ref Helper.ThrowAOORE<int>(index, "invalid index, must be in 0..<10")
				: ref Unsafe.Add(ref GetPinnableReference(), index);

		public unsafe ref int GetPinnableReference()
			=> ref _element0;
	}
}
#pragma warning restore CS9084

The final challenge to solve is computing the width of a struct at compile-time. (To allow elements to be of any unmanaged type, which is required for this to be more useful than an unsafe fixed field.) It was working fine, Guid is just weird. Now that's working, I don't see why I couldn't also allow for extra fields before the array part, which not even [InlineArray] can do.
Also the Source Generator wouldn't detect any applications of [InlineArray] for some reason, so I had to make a custom attribute.
Based on scattered docs I've assumed that [StructLayout(Size = ...)] will be sufficient for the unmanaged side, and I'm fairly confident that the Unsafe/Span hacks will work for the managed side. If it turns out [StructLayout(Size = ...)] doesn't work and it's some extra metadata in the IL, I could just generate more fields _element1 through _elementN to get the right size.

see also #3393

@YoshiRulz YoshiRulz marked this pull request as draft September 26, 2025 23:57
@YoshiRulz YoshiRulz added Request: Feature/Enhancement For feature requests or possible improvements Meta Relating to code organisation or to things that aren't code labels Sep 27, 2025
@YoshiRulz YoshiRulz changed the title [InlineArray] polyfill Polyfill [InlineArray] with a new Source Generator Sep 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Meta Relating to code organisation or to things that aren't code Needs domain knowledge for triage Request: Feature/Enhancement For feature requests or possible improvements
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant