Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
59 changes: 59 additions & 0 deletions BitsKit.Benchmarks/BitsKitBenchmark.GeneratedAccessors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public partial class BitsKitBenchmark
private readonly GeneratedAccessorLsbModel[] _generatedAccessorGetModels = CreateGeneratedAccessorModels();
private readonly GeneratedAccessorLsbModel[] _generatedAccessorSetModels = CreateGeneratedAccessorModels();
private readonly GeneratedAccessorMemoryModel[] _generatedAccessorMemoryModels = CreateGeneratedAccessorMemoryModels();
private readonly GeneratedAccessorAlignedMemoryModel[] _generatedAccessorAlignedMemoryModels = CreateGeneratedAccessorAlignedMemoryModels();
private readonly GeneratedAccessorInlineArrayModel[] _generatedAccessorInlineArrayModels = CreateGeneratedAccessorInlineArrayModels();

[Benchmark(OperationsPerInvoke = AccessorOperations)]
Expand Down Expand Up @@ -157,6 +158,50 @@ public uint GeneratedAccessorSetMemoryLSB()
return BitConverter.ToUInt32(_generatedAccessorMemoryModels[0].BackingField.Span);
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "Memory", "Aligned", "Get", "UInt32", "LSB")]
public uint GeneratedAccessorGetAlignedMemoryUInt32LSB()
{
uint sum = 0;

for (int i = 0; i < AccessorOperations; i++)
sum += _generatedAccessorAlignedMemoryModels[i & AccessorModelMask].UInt32Value;

return sum;
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "Memory", "Aligned", "Set", "UInt32", "LSB")]
public uint GeneratedAccessorSetAlignedMemoryUInt32LSB()
{
for (int i = 0; i < AccessorOperations; i++)
_generatedAccessorAlignedMemoryModels[i & AccessorModelMask].UInt32Value = (uint)i;

return BitConverter.ToUInt32(_generatedAccessorAlignedMemoryModels[0].UInt32BackingField.Span);
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "Memory", "Aligned", "Get", "UInt64", "LSB")]
public ulong GeneratedAccessorGetAlignedMemoryUInt64LSB()
{
ulong sum = 0;

for (int i = 0; i < AccessorOperations; i++)
sum += _generatedAccessorAlignedMemoryModels[i & AccessorModelMask].UInt64Value;

return sum;
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "Memory", "Aligned", "Set", "UInt64", "LSB")]
public ulong GeneratedAccessorSetAlignedMemoryUInt64LSB()
{
for (int i = 0; i < AccessorOperations; i++)
_generatedAccessorAlignedMemoryModels[i & AccessorModelMask].UInt64Value = (ulong)i;

return BitConverter.ToUInt64(_generatedAccessorAlignedMemoryModels[0].UInt64BackingField.Span);
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "InlineArray", "Get", "LSB")]
public uint GeneratedAccessorGetInlineArrayLSB()
Expand Down Expand Up @@ -218,4 +263,18 @@ private static GeneratedAccessorInlineArrayModel[] CreateGeneratedAccessorInline

return models;
}

private static GeneratedAccessorAlignedMemoryModel[] CreateGeneratedAccessorAlignedMemoryModels()
{
var models = new GeneratedAccessorAlignedMemoryModel[AccessorModelCount];

for (int i = 0; i < models.Length; i++)
{
ulong value = unchecked((ulong)i * 0x9E3779B97F4A7C15UL);
models[i].UInt32BackingField = BitConverter.GetBytes((uint)value);
models[i].UInt64BackingField = BitConverter.GetBytes(value);
}

return models;
}
}
10 changes: 10 additions & 0 deletions BitsKit.Benchmarks/GeneratedAccessorBenchmarkModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ public partial struct GeneratedAccessorMemoryModel
public Memory<byte> BackingField;
}

[BitObject(BitOrder.LeastSignificant)]
public partial struct GeneratedAccessorAlignedMemoryModel
{
[BitField("UInt32Value", 32, BitFieldType.UInt32)]
public Memory<byte> UInt32BackingField;

[BitField("UInt64Value", 64, BitFieldType.UInt64)]
public Memory<byte> UInt64BackingField;
}

#if NET8_0_OR_GREATER

[BitObject(BitOrder.LeastSignificant)]
Expand Down
64 changes: 64 additions & 0 deletions BitsKit.Generator/Models/BitFieldModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,70 @@ BackingFieldType.Span or
_ => throw new NotSupportedException()
};

/// <summary>
/// Creates a direct endian-aware read for byte-aligned, full-width memory fields.
/// </summary>
protected bool TryGetDirectMemoryReadExpression(out string expression)
{
expression = string.Empty;

if (!TryGetDirectMemoryInfo(out int width, out string typeName, out string source))
return false;

if (width == 8)
{
expression = $"unchecked(({typeName}){source}[0])";
return true;
}

string endianness = BitOrder == BitOrder.MostSignificant ? "BigEndian" : "LittleEndian";
expression = $"BinaryPrimitives.Read{typeName}{endianness}({source})";
return true;
}

/// <summary>
/// Creates a direct endian-aware write for byte-aligned, full-width memory fields.
/// </summary>
protected bool TryGetDirectMemoryWriteExpression(string valueExpression, out string expression)
{
expression = string.Empty;

if (!TryGetDirectMemoryInfo(out int width, out string typeName, out string source))
return false;

string value = $"unchecked(({typeName})({valueExpression}))";
if (width == 8)
{
expression = $"{source}[0] = unchecked((Byte){value})";
return true;
}

string endianness = BitOrder == BitOrder.MostSignificant ? "BigEndian" : "LittleEndian";
expression = $"BinaryPrimitives.Write{typeName}{endianness}({source}, {value})";
return true;
}

private bool TryGetDirectMemoryInfo(out int width, out string typeName, out string source)
{
width = FieldType switch
{
BitFieldType.SByte or BitFieldType.Byte => 8,
BitFieldType.Int16 or BitFieldType.UInt16 => 16,
BitFieldType.Int32 or BitFieldType.UInt32 => 32,
BitFieldType.Int64 or BitFieldType.UInt64 => 64,
_ => 0
};
typeName = FieldType?.ToString() ?? string.Empty;

int byteOffset = BitOffset >> 3;
source = byteOffset == 0 ? "{4}.Span" : $"{{4}}.Span.Slice({byteOffset})";

return BackingFieldType == BackingFieldType.Memory &&
width != 0 &&
BitCount == width &&
(BitOffset & 7) == 0;
}

/// <summary>
/// Creates a specialized scalar read for fixed-width integral backing fields.
/// </summary>
Expand Down
6 changes: 4 additions & 2 deletions BitsKit.Generator/Models/EnumFieldModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,17 @@ public EnumFieldModel(AttributeData attributeData, TypeSymbolProcessor? typeSymb

protected override string GetGetterTemplate()
{
if (TryGetDirectIntegralReadExpression(out string expression))
if (TryGetDirectMemoryReadExpression(out string expression) ||
TryGetDirectIntegralReadExpression(out expression))
return $"{{0}} {{1}} => ({ReturnType})({expression});";

return string.Format(StringConstants.ExplicitGetterTemplate, GetterSource(), ReturnType);
}

protected override string GetSetterTemplate()
{
if (TryGetDirectIntegralWriteExpression("value", out string expression))
if (TryGetDirectMemoryWriteExpression("value", out string expression) ||
TryGetDirectIntegralWriteExpression("value", out expression))
return "{0} {1} => " + expression + ";";

return string.Format(StringConstants.ExplicitSetterTemplate, SetterSource(), FieldType);
Expand Down
6 changes: 4 additions & 2 deletions BitsKit.Generator/Models/IntegralFieldModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public IntegralFieldModel(AttributeData attributeData, TypeSymbolProcessor? type

protected override string GetGetterTemplate()
{
if (TryGetDirectIntegralReadExpression(out string expression))
if (TryGetDirectMemoryReadExpression(out string expression) ||
TryGetDirectIntegralReadExpression(out expression))
{
if (IsTypeCast)
expression = $"({ReturnType})({expression})";
Expand All @@ -58,7 +59,8 @@ protected override string GetGetterTemplate()

protected override string GetSetterTemplate()
{
if (TryGetDirectIntegralWriteExpression("value", out string expression))
if (TryGetDirectMemoryWriteExpression("value", out string expression) ||
TryGetDirectIntegralWriteExpression("value", out expression))
return "{0} {1} => " + expression + ";";

if (IsTypeCast)
Expand Down
38 changes: 38 additions & 0 deletions BitsKit.Tests/GeneratorTests.Models.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,44 @@ public partial struct OptimizedMemoryAccessorStruct
public Memory<byte> Backing;
}

[BitObject(BitOrder.LeastSignificant)]
public partial struct AlignedMemoryAccessorStruct
{
[BitField("ByteValue", 8, BitFieldType.Byte)]
public Memory<byte> ByteBacking;

[BitField("SignedByteValue", 8, BitFieldType.SByte)]
public Memory<byte> SignedByteBacking;

[BitField("UInt16Value", 16, BitFieldType.UInt16)]
public Memory<byte> UInt16Backing;

[BitField("Int32Value", 32, BitFieldType.Int32)]
public Memory<byte> Int32Backing;

[BitField("UInt64Value", 64, BitFieldType.UInt64)]
public Memory<byte> UInt64Backing;

[BitField("UInt16BigEndianValue", 16, BitFieldType.UInt16, ReverseBitOrder = true)]
public Memory<byte> UInt16BigEndianBacking;

[BitField("Int32BigEndianValue", 32, BitFieldType.Int32, ReverseBitOrder = true)]
public Memory<byte> Int32BigEndianBacking;

[BitField("UInt64BigEndianValue", 64, BitFieldType.UInt64, ReverseBitOrder = true)]
public Memory<byte> UInt64BigEndianBacking;

[BitField(8)]
[BitField("OffsetUInt32Value", 32, BitFieldType.UInt32)]
public Memory<byte> OffsetUInt32Backing;

[BitField("ReadOnlyUInt32Value", 32, BitFieldType.UInt32)]
public ReadOnlyMemory<byte> ReadOnlyUInt32Backing;

[EnumField("EnumValue", 32, typeof(TestEnum))]
public Memory<byte> EnumBacking;
}

#if NET8_0_OR_GREATER

[BitObject(BitOrder.LeastSignificant)]
Expand Down
88 changes: 88 additions & 0 deletions BitsKit.Tests/GeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,94 @@ public void OptimizedStorageGettersMatchBitPrimitives()
}
}

[TestMethod]
public void AlignedMemoryAccessorsMatchBitPrimitives()
{
Random random = new(0xA119ED);

for (int i = 0; i < 1000; i++)
{
byte[] byteBytes = new byte[1];
byte[] signedByteBytes = new byte[1];
byte[] uint16Bytes = new byte[2];
byte[] int32Bytes = new byte[4];
byte[] uint64Bytes = new byte[8];
byte[] uint16BigEndianBytes = new byte[2];
byte[] int32BigEndianBytes = new byte[4];
byte[] uint64BigEndianBytes = new byte[8];
byte[] offsetUInt32Bytes = new byte[5];
byte[] readOnlyUInt32Bytes = new byte[4];
byte[] enumBytes = new byte[4];

foreach (byte[] bytes in new[]
{
byteBytes, signedByteBytes, uint16Bytes, int32Bytes, uint64Bytes,
uint16BigEndianBytes, int32BigEndianBytes, uint64BigEndianBytes,
offsetUInt32Bytes, readOnlyUInt32Bytes, enumBytes
})
{
random.NextBytes(bytes);
}

var actual = new AlignedMemoryAccessorStruct
{
ByteBacking = byteBytes,
SignedByteBacking = signedByteBytes,
UInt16Backing = uint16Bytes,
Int32Backing = int32Bytes,
UInt64Backing = uint64Bytes,
UInt16BigEndianBacking = uint16BigEndianBytes,
Int32BigEndianBacking = int32BigEndianBytes,
UInt64BigEndianBacking = uint64BigEndianBytes,
OffsetUInt32Backing = offsetUInt32Bytes,
ReadOnlyUInt32Backing = readOnlyUInt32Bytes,
EnumBacking = enumBytes
};

Assert.AreEqual(BitPrimitives.ReadUInt8LSB(byteBytes, 0, 8), actual.ByteValue);
Assert.AreEqual(BitPrimitives.ReadInt8LSB(signedByteBytes, 0, 8), actual.SignedByteValue);
Assert.AreEqual(BitPrimitives.ReadUInt16LSB(uint16Bytes, 0, 16), actual.UInt16Value);
Assert.AreEqual(BitPrimitives.ReadInt32LSB(int32Bytes, 0, 32), actual.Int32Value);
Assert.AreEqual(BitPrimitives.ReadUInt64LSB(uint64Bytes, 0, 64), actual.UInt64Value);
Assert.AreEqual(BitPrimitives.ReadUInt16MSB(uint16BigEndianBytes, 0, 16), actual.UInt16BigEndianValue);
Assert.AreEqual(BitPrimitives.ReadInt32MSB(int32BigEndianBytes, 0, 32), actual.Int32BigEndianValue);
Assert.AreEqual(BitPrimitives.ReadUInt64MSB(uint64BigEndianBytes, 0, 64), actual.UInt64BigEndianValue);
Assert.AreEqual(BitPrimitives.ReadUInt32LSB(offsetUInt32Bytes, 8, 32), actual.OffsetUInt32Value);
Assert.AreEqual(BitPrimitives.ReadUInt32LSB(readOnlyUInt32Bytes, 0, 32), actual.ReadOnlyUInt32Value);
Assert.AreEqual((TestEnum)BitPrimitives.ReadUInt32LSB(enumBytes, 0, 32), actual.EnumValue);

byte nextByte = (byte)random.Next();
sbyte nextSignedByte = unchecked((sbyte)random.Next());
ushort nextUInt16 = (ushort)random.Next();
int nextInt32 = random.Next();
ulong nextUInt64 = unchecked((ulong)random.NextInt64());
uint nextOffsetUInt32 = unchecked((uint)random.NextInt64());
TestEnum nextEnum = (TestEnum)(uint)random.Next(4);

actual.ByteValue = nextByte;
actual.SignedByteValue = nextSignedByte;
actual.UInt16Value = nextUInt16;
actual.Int32Value = nextInt32;
actual.UInt64Value = nextUInt64;
actual.UInt16BigEndianValue = nextUInt16;
actual.Int32BigEndianValue = nextInt32;
actual.UInt64BigEndianValue = nextUInt64;
actual.OffsetUInt32Value = nextOffsetUInt32;
actual.EnumValue = nextEnum;

Assert.AreEqual(nextByte, BitPrimitives.ReadUInt8LSB(byteBytes, 0, 8));
Assert.AreEqual(nextSignedByte, BitPrimitives.ReadInt8LSB(signedByteBytes, 0, 8));
Assert.AreEqual(nextUInt16, BitPrimitives.ReadUInt16LSB(uint16Bytes, 0, 16));
Assert.AreEqual(nextInt32, BitPrimitives.ReadInt32LSB(int32Bytes, 0, 32));
Assert.AreEqual(nextUInt64, BitPrimitives.ReadUInt64LSB(uint64Bytes, 0, 64));
Assert.AreEqual(nextUInt16, BitPrimitives.ReadUInt16MSB(uint16BigEndianBytes, 0, 16));
Assert.AreEqual(nextInt32, BitPrimitives.ReadInt32MSB(int32BigEndianBytes, 0, 32));
Assert.AreEqual(nextUInt64, BitPrimitives.ReadUInt64MSB(uint64BigEndianBytes, 0, 64));
Assert.AreEqual(nextOffsetUInt32, BitPrimitives.ReadUInt32LSB(offsetUInt32Bytes, 8, 32));
Assert.AreEqual(nextEnum, (TestEnum)BitPrimitives.ReadUInt32LSB(enumBytes, 0, 32));
}
}

[TestMethod]
public void ReadOnlyMemberTest()
{
Expand Down
8 changes: 6 additions & 2 deletions eng/Run-Benchmark-Regression.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,16 @@ function Get-ComparableBenchmarkMethods {
'GeneratedAccessorSetEnumMSB',
'GeneratedAccessorGetMemoryLSB',
'GeneratedAccessorSetMemoryLSB',
'GeneratedAccessorGetAlignedMemoryUInt32LSB',
'GeneratedAccessorSetAlignedMemoryUInt32LSB',
'GeneratedAccessorGetAlignedMemoryUInt64LSB',
'GeneratedAccessorSetAlignedMemoryUInt64LSB',
'GeneratedAccessorGetInlineArrayLSB') {
$methods.Add($method)
}

if ($methods.Count -ne 70) {
throw "Expected 70 comparable benchmark methods, found $($methods.Count)."
if ($methods.Count -ne 74) {
throw "Expected 74 comparable benchmark methods, found $($methods.Count)."
}

return $methods
Expand Down
Loading