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
114 changes: 114 additions & 0 deletions BitsKit.Benchmarks/BitsKitBenchmark.GeneratedAccessors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public partial class BitsKitBenchmark
private readonly GeneratedAccessorLsbModel[] _generatedAccessorSetModels = CreateGeneratedAccessorModels();
private readonly GeneratedAccessorMemoryModel[] _generatedAccessorMemoryModels = CreateGeneratedAccessorMemoryModels();
private readonly GeneratedAccessorAlignedMemoryModel[] _generatedAccessorAlignedMemoryModels = CreateGeneratedAccessorAlignedMemoryModels();
private readonly GeneratedAccessorCheckedAccessModel[] _generatedAccessorCheckedAccessModels = CreateGeneratedAccessorCheckedAccessModels();
private readonly GeneratedAccessorUnsafeAccessModel[] _generatedAccessorUnsafeAccessModels = CreateGeneratedAccessorUnsafeAccessModels();
private readonly byte[][] _generatedAccessorAlignedSpanBuffers = CreateGeneratedAccessorAlignedSpanBuffers();
private readonly GeneratedAccessorInlineArrayModel[] _generatedAccessorInlineArrayModels = CreateGeneratedAccessorInlineArrayModels();
private readonly GeneratedAccessorAlignedInlineArrayModel[] _generatedAccessorAlignedInlineArrayModels = CreateGeneratedAccessorAlignedInlineArrayModels();
Expand Down Expand Up @@ -292,6 +294,82 @@ public ulong GeneratedAccessorSetAlignedMemoryUInt64LSB()
return BitConverter.ToUInt64(_generatedAccessorAlignedMemoryModels[0].UInt64BackingField.Span);
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "CheckedAccess", "Memory", "Get", "20", "LSB")]
public uint GeneratedAccessorAccessComparisonCheckedGetMemory20LSB()
{
uint sum = 0;
for (int i = 0; i < AccessorOperations; i++)
sum += _generatedAccessorCheckedAccessModels[i & AccessorModelMask].Value20;
return sum;
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "UnsafeAccess", "Memory", "Get", "20", "LSB")]
public uint GeneratedAccessorAccessComparisonUnsafeGetMemory20LSB()
{
uint sum = 0;
for (int i = 0; i < AccessorOperations; i++)
sum += _generatedAccessorUnsafeAccessModels[i & AccessorModelMask].Value20;
return sum;
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "CheckedAccess", "Memory", "Set", "20", "LSB")]
public uint GeneratedAccessorAccessComparisonCheckedSetMemory20LSB()
{
for (int i = 0; i < AccessorOperations; i++)
_generatedAccessorCheckedAccessModels[i & AccessorModelMask].Value20 = (uint)i;
return BitConverter.ToUInt32(_generatedAccessorCheckedAccessModels[0].Value20BackingField.Span);
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "UnsafeAccess", "Memory", "Set", "20", "LSB")]
public uint GeneratedAccessorAccessComparisonUnsafeSetMemory20LSB()
{
for (int i = 0; i < AccessorOperations; i++)
_generatedAccessorUnsafeAccessModels[i & AccessorModelMask].Value20 = (uint)i;
return BitConverter.ToUInt32(_generatedAccessorUnsafeAccessModels[0].Value20BackingField.Span);
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "CheckedAccess", "Memory", "Boolean", "Get", "LSB")]
public int GeneratedAccessorAccessComparisonCheckedGetMemoryBooleanLSB()
{
int count = 0;
for (int i = 0; i < AccessorOperations; i++)
count += _generatedAccessorCheckedAccessModels[i & AccessorModelMask].Flag ? 1 : 0;
return count;
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "UnsafeAccess", "Memory", "Boolean", "Get", "LSB")]
public int GeneratedAccessorAccessComparisonUnsafeGetMemoryBooleanLSB()
{
int count = 0;
for (int i = 0; i < AccessorOperations; i++)
count += _generatedAccessorUnsafeAccessModels[i & AccessorModelMask].Flag ? 1 : 0;
return count;
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "CheckedAccess", "Memory", "Boolean", "Set", "LSB")]
public byte GeneratedAccessorAccessComparisonCheckedSetMemoryBooleanLSB()
{
for (int i = 0; i < AccessorOperations; i++)
_generatedAccessorCheckedAccessModels[i & AccessorModelMask].Flag = (i & 1) != 0;
return _generatedAccessorCheckedAccessModels[0].BooleanBackingField.Span[0];
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "UnsafeAccess", "Memory", "Boolean", "Set", "LSB")]
public byte GeneratedAccessorAccessComparisonUnsafeSetMemoryBooleanLSB()
{
for (int i = 0; i < AccessorOperations; i++)
_generatedAccessorUnsafeAccessModels[i & AccessorModelMask].Flag = (i & 1) != 0;
return _generatedAccessorUnsafeAccessModels[0].BooleanBackingField.Span[0];
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "Span", "Aligned", "Get", "UInt32", "LSB")]
public uint GeneratedAccessorGetAlignedSpanUInt32LSB()
Expand Down Expand Up @@ -462,6 +540,42 @@ private static GeneratedAccessorAlignedMemoryModel[] CreateGeneratedAccessorAlig
return models;
}

private static GeneratedAccessorCheckedAccessModel[] CreateGeneratedAccessorCheckedAccessModels()
{
var models = new GeneratedAccessorCheckedAccessModel[AccessorModelCount];

for (int i = 0; i < models.Length; i++)
{
byte[] value20 = new byte[16];
byte[] boolean = new byte[16];
uint value = unchecked((uint)i * 0x9E3779B9u);
BitConverter.TryWriteBytes(value20, value);
boolean[0] = (byte)value;
models[i].Value20BackingField = value20;
models[i].BooleanBackingField = boolean;
}

return models;
}

private static GeneratedAccessorUnsafeAccessModel[] CreateGeneratedAccessorUnsafeAccessModels()
{
var models = new GeneratedAccessorUnsafeAccessModel[AccessorModelCount];

for (int i = 0; i < models.Length; i++)
{
byte[] value20 = new byte[16];
byte[] boolean = new byte[16];
uint value = unchecked((uint)i * 0x9E3779B9u);
BitConverter.TryWriteBytes(value20, value);
boolean[0] = (byte)value;
models[i].Value20BackingField = value20;
models[i].BooleanBackingField = boolean;
}

return models;
}

private static byte[][] CreateGeneratedAccessorAlignedSpanBuffers()
{
var buffers = new byte[AccessorModelCount][];
Expand Down
24 changes: 24 additions & 0 deletions BitsKit.Benchmarks/GeneratedAccessorBenchmarkModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,30 @@ public partial struct GeneratedAccessorAlignedMemoryModel
public Memory<byte> UInt64BackingField;
}

[BitObject(BitOrder.LeastSignificant)]
public partial struct GeneratedAccessorCheckedAccessModel
{
[BitField(3)]
[BitField("Value20", 20, BitFieldType.UInt32)]
public Memory<byte> Value20BackingField;

[BitField(5)]
[BooleanField("Flag")]
public Memory<byte> BooleanBackingField;
}

[BitObject(BitOrder.LeastSignificant, AccessMode = BitObjectAccessMode.Unsafe)]
public partial struct GeneratedAccessorUnsafeAccessModel
{
[BitField(3)]
[BitField("Value20", 20, BitFieldType.UInt32)]
public Memory<byte> Value20BackingField;

[BitField(5)]
[BooleanField("Flag")]
public Memory<byte> BooleanBackingField;
}

[BitObject(BitOrder.LeastSignificant)]
public ref partial struct GeneratedAccessorAlignedSpanModel
{
Expand Down
6 changes: 6 additions & 0 deletions BitsKit.Generator/Enums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ internal enum BitOrder
MostSignificant
}

internal enum BitObjectAccessMode
{
Checked,
Unsafe
}

/// <summary>
/// An enumeration listing all of the options to modify bit-field generation
/// </summary>
Expand Down
123 changes: 123 additions & 0 deletions BitsKit.Generator/Models/BitFieldModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal abstract record BitFieldModel
public BitFieldModifiers Modifiers { get; }

private readonly bool _containingTypeIsStruct;
protected bool UsesUnsafeAccess { get; }

public BitFieldModel(AttributeData attributeData, TypeSymbolProcessor? typeSymbol)
{
Expand All @@ -28,6 +29,7 @@ public BitFieldModel(AttributeData attributeData, TypeSymbolProcessor? typeSymbo

_containingTypeIsStruct = typeSymbol.IsStruct;
BitOrder = typeSymbol.DefaultBitOrder;
UsesUnsafeAccess = typeSymbol.AccessMode == BitObjectAccessMode.Unsafe;
}

for (int i = 0; i < attributeData.NamedArguments.Length; i++)
Expand Down Expand Up @@ -181,6 +183,127 @@ BackingFieldType.Span or
_ => throw new NotSupportedException()
};

/// <summary>
/// Creates an unchecked read through a raw reference for opted-in byte-addressable storage.
/// </summary>
protected bool TryGetUnsafeReadExpression(out string expression)
{
expression = string.Empty;
if (UsesFasterCheckedStorageSpecialization())
return false;

if (!TryGetUnsafeStorageReference(writable: false, out string source))
return false;

expression = $"UnsafeBitPrimitives.Read{FieldType!.Value.ToIntegralName()}{BitOrder.ToShortName()}" +
$"({source}, {BitOffset}, {BitCount})";
return true;
}

/// <summary>
/// Creates an unchecked write through a raw reference for opted-in byte-addressable storage.
/// </summary>
protected bool TryGetUnsafeWriteExpression(string valueExpression, out string expression)
{
expression = string.Empty;
if (UsesFasterCheckedStorageSpecialization())
return false;

if (!TryGetUnsafeStorageReference(writable: true, out string destination))
return false;

expression = $"UnsafeBitPrimitives.Write{FieldType!.Value.ToIntegralName()}{BitOrder.ToShortName()}" +
$"({destination}, {BitOffset}, unchecked(({FieldType.Value})({valueExpression})), {BitCount})";
return true;
}

/// <summary>
/// Creates an unchecked single-bit read through a raw reference.
/// </summary>
protected bool TryGetUnsafeBooleanReadExpression(out string expression)
{
expression = string.Empty;
if (!TryGetUnsafeStorageTarget(writable: false, out string source))
return false;

int byteOffset = BitOffset >> 3;
int bitInByte = BitOffset & 7;
int mask = 1 << (BitOrder == BitOrder.MostSignificant ? 7 - bitInByte : bitInByte);
string target = byteOffset == 0
? source
: $"System.Runtime.CompilerServices.Unsafe.Add(ref {source}, {byteOffset})";
expression = $"({target} & 0x{mask:X2}) != 0";
return true;
}

/// <summary>
/// Creates an unchecked single-bit write through a raw reference.
/// </summary>
protected bool TryGetUnsafeBooleanWriteTemplate(out string template)
{
template = string.Empty;
if (!TryGetUnsafeStorageTarget(writable: true, out string destination))
return false;

int byteOffset = BitOffset >> 3;
int bitInByte = BitOffset & 7;
int mask = 1 << (BitOrder == BitOrder.MostSignificant ? 7 - bitInByte : bitInByte);
string target = byteOffset == 0
? destination
: $"System.Runtime.CompilerServices.Unsafe.Add(ref {destination}, {byteOffset})";
template =
"{0} {1}\n" +
"{{\n" +
$" ref Byte target = ref {target};\n" +
" if (value)\n" +
$" target |= 0x{mask:X2};\n" +
" else\n" +
$" target &= 0x{255 ^ mask:X2};\n" +
"}}";
return true;
}

private bool TryGetUnsafeStorageReference(bool writable, out string reference)
{
reference = string.Empty;
if (!TryGetUnsafeStorageTarget(writable, out string target))
return false;

reference = $"ref {target}";
return true;
}

private bool TryGetUnsafeStorageTarget(bool writable, out string target)
{
target = string.Empty;
if (!UsesUnsafeAccess || BackingFieldType == BackingFieldType.Integral)
return false;

string source = BackingField.TypeString == "byte[]"
? writable ? "((Span<Byte>){4})" : "((ReadOnlySpan<Byte>){4})"
: writable ? SetterSource() : GetterSource();
target = $"MemoryMarshal.GetReference({source})";
return true;
}

private bool UsesFasterCheckedStorageSpecialization()
{
int 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
};

return UsesUnsafeAccess &&
BackingFieldType is BackingFieldType.Memory or BackingFieldType.Span or BackingFieldType.InlineArray &&
width != 0 &&
BitCount == width &&
(BitOffset & 7) == 0;
}

/// <summary>
/// Creates a direct endian-aware read for byte-aligned, full-width byte storage.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions BitsKit.Generator/Models/BooleanFieldModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public BooleanFieldModel(AttributeData attributeData, TypeSymbolProcessor? typeS

protected override string GetGetterTemplate()
{
if (TryGetUnsafeBooleanReadExpression(out string unsafeExpression))
return "{0} {1} => " + unsafeExpression + ";";

if (TryGetDirectStorageBooleanReadExpression(out string expression) ||
TryGetDirectIntegralBooleanReadExpression(out expression))
return "{0} {1} => " + expression + ";";
Expand All @@ -43,6 +46,9 @@ protected override string GetGetterTemplate()

protected override string GetSetterTemplate()
{
if (TryGetUnsafeBooleanWriteTemplate(out string unsafeTemplate))
return unsafeTemplate;

if (TryGetDirectStorageBooleanWriteTemplate(out string template))
return template;

Expand Down
6 changes: 6 additions & 0 deletions BitsKit.Generator/Models/EnumFieldModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ public EnumFieldModel(AttributeData attributeData, TypeSymbolProcessor? typeSymb

protected override string GetGetterTemplate()
{
if (TryGetUnsafeReadExpression(out string unsafeExpression))
return $"{{0}} {{1}} => ({ReturnType})({unsafeExpression});";

if (TryGetDirectFixedWidthReadTemplate(out string template))
return template;

Expand All @@ -62,6 +65,9 @@ protected override string GetGetterTemplate()

protected override string GetSetterTemplate()
{
if (TryGetUnsafeWriteExpression("value", out string unsafeExpression))
return "{0} {1} => " + unsafeExpression + ";";

if (TryGetDirectFixedWidthWriteTemplate("value", out string template))
return template;

Expand Down
6 changes: 6 additions & 0 deletions BitsKit.Generator/Models/IntegralFieldModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ public IntegralFieldModel(AttributeData attributeData, TypeSymbolProcessor? type

protected override string GetGetterTemplate()
{
if (TryGetUnsafeReadExpression(out string unsafeExpression))
return "{0} {1} => " + unsafeExpression + ";";

if (TryGetDirectFixedWidthReadTemplate(out string template))
return template;

Expand All @@ -62,6 +65,9 @@ protected override string GetGetterTemplate()

protected override string GetSetterTemplate()
{
if (TryGetUnsafeWriteExpression("value", out string unsafeExpression))
return "{0} {1} => " + unsafeExpression + ";";

if (TryGetDirectFixedWidthWriteTemplate("value", out string template))
return template;

Expand Down
Loading
Loading