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
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<BitsKitProjectPath Condition="'$(BitsKitProjectPath)' == ''">$(MSBuildThisFileDirectory)..\BitsKit\BitsKit.csproj</BitsKitProjectPath>
<BitsKitGeneratorProjectPath Condition="'$(BitsKitGeneratorProjectPath)' == ''">$(MSBuildThisFileDirectory)..\BitsKit.Generator\BitsKit.Generator.csproj</BitsKitGeneratorProjectPath>
</PropertyGroup>

<ItemGroup>
Expand All @@ -14,11 +15,13 @@

<ItemGroup>
<ProjectReference Include="$(BitsKitProjectPath)" AdditionalProperties="TargetFramework=net8.0" />
<ProjectReference Include="$(BitsKitGeneratorProjectPath)" AdditionalProperties="TargetFramework=netstandard2.0" ReferenceOutputAssembly="false" OutputItemType="Analyzer" />
</ItemGroup>

<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="..\BitsKit.Benchmarks\BitsKitBenchmark.cs" Link="BitsKitBenchmark.cs" />
<Compile Include="..\BitsKit.Benchmarks\BitsKitBenchmark.GeneratedAccessors.cs" Link="BitsKitBenchmark.GeneratedAccessors.cs" />
<Compile Include="..\BitsKit.Benchmarks\BitsKitBenchmark.BitReader.cs" Link="BitsKitBenchmark.BitReader.cs" />
<Compile Include="..\BitsKit.Benchmarks\BitsKitBenchmark.BitStreamReader.cs" Link="BitsKitBenchmark.BitStreamReader.cs" />
<Compile Include="..\BitsKit.Benchmarks\BitsKitBenchmark.BitStreamWriter.cs" Link="BitsKitBenchmark.BitStreamWriter.cs" />
Expand All @@ -29,6 +32,7 @@
<Compile Include="..\BitsKit.Benchmarks\BitsKitBenchmark.ReadMSB.cs" Link="BitsKitBenchmark.ReadMSB.cs" />
<Compile Include="..\BitsKit.Benchmarks\BitsKitBenchmark.WriteLSB.cs" Link="BitsKitBenchmark.WriteLSB.cs" />
<Compile Include="..\BitsKit.Benchmarks\BitsKitBenchmark.WriteMSB.cs" Link="BitsKitBenchmark.WriteMSB.cs" />
<Compile Include="..\BitsKit.Benchmarks\GeneratedAccessorBenchmarkModels.cs" Link="GeneratedAccessorBenchmarkModels.cs" />
<Compile Include="..\BitsKit.Tests\Helpers.cs" Link="Helpers.cs" />
</ItemGroup>

Expand Down
139 changes: 136 additions & 3 deletions BitsKit.Benchmarks/BitsKitBenchmark.GeneratedAccessors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ public partial class BitsKitBenchmark

private readonly GeneratedAccessorLsbModel[] _generatedAccessorGetModels = CreateGeneratedAccessorModels();
private readonly GeneratedAccessorLsbModel[] _generatedAccessorSetModels = CreateGeneratedAccessorModels();
private readonly GeneratedAccessorMemoryModel[] _generatedAccessorMemoryModels = CreateGeneratedAccessorMemoryModels();
private readonly GeneratedAccessorInlineArrayModel[] _generatedAccessorInlineArrayModels = CreateGeneratedAccessorInlineArrayModels();

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "Integral", "Get", "LSB")]
public uint GeneratedAccessorGet()
public uint GeneratedAccessorGetUInt32LSB()
{
uint sum = 0;

Expand All @@ -25,20 +27,151 @@ public uint GeneratedAccessorGet()

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "Integral", "Set", "LSB")]
public uint GeneratedAccessorSet()
public uint GeneratedAccessorSetUInt32LSB()
{
for (int i = 0; i < AccessorOperations; i++)
_generatedAccessorSetModels[i & AccessorModelMask].Value = (uint)i;

return _generatedAccessorSetModels[0].BackingField;
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "Integral", "Get", "Signed", "LSB")]
public int GeneratedAccessorGetInt32LSB()
{
int sum = 0;

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

return sum;
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "Boolean", "Get", "LSB")]
public int GeneratedAccessorGetBooleanLSB()
{
int count = 0;

for (int i = 0; i < AccessorOperations; i++)
count += _generatedAccessorGetModels[i & AccessorModelMask].Flag ? 1 : 0;

return count;
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "Enum", "Get", "LSB")]
public uint GeneratedAccessorGetEnumLSB()
{
uint sum = 0;

for (int i = 0; i < AccessorOperations; i++)
sum += (uint)_generatedAccessorGetModels[i & AccessorModelMask].Kind;

return sum;
}

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

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

return sum;
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "Integral", "Get", "MSB")]
public uint GeneratedAccessorGetUInt32MSB()
{
uint sum = 0;

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

return sum;
}

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

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

return sum;
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "Memory", "Set", "LSB")]
public uint GeneratedAccessorSetMemoryLSB()
{
for (int i = 0; i < AccessorOperations; i++)
_generatedAccessorMemoryModels[i & AccessorModelMask].Value = (uint)i;

return BitConverter.ToUInt32(_generatedAccessorMemoryModels[0].BackingField.Span);
}

[Benchmark(OperationsPerInvoke = AccessorOperations)]
[BenchmarkCategory("GeneratedAccessor", "InlineArray", "Get", "LSB")]
public uint GeneratedAccessorGetInlineArrayLSB()
{
uint sum = 0;

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

return sum;
}

private static GeneratedAccessorLsbModel[] CreateGeneratedAccessorModels()
{
var models = new GeneratedAccessorLsbModel[AccessorModelCount];

for (int i = 0; i < models.Length; i++)
models[i].BackingField = unchecked((uint)i * 0x9E3779B9u);
{
uint value = unchecked((uint)i * 0x9E3779B9u);
models[i].BackingField = value;
models[i].SignedBackingField = unchecked((int)(value ^ 0xA5A5A5A5u));
models[i].BooleanBackingField = value;
models[i].EnumBackingField = value;
models[i].WideBackingField = ((ulong)value << 32) | ~value;
models[i].MostSignificantBackingField = value;
}

return models;
}

private static GeneratedAccessorMemoryModel[] CreateGeneratedAccessorMemoryModels()
{
var models = new GeneratedAccessorMemoryModel[AccessorModelCount];

for (int i = 0; i < models.Length; i++)
{
uint value = unchecked((uint)i * 0x9E3779B9u);
models[i].BackingField = BitConverter.GetBytes(value);
}

return models;
}

private static GeneratedAccessorInlineArrayModel[] CreateGeneratedAccessorInlineArrayModels()
{
var models = new GeneratedAccessorInlineArrayModel[AccessorModelCount];

for (int i = 0; i < models.Length; i++)
{
uint value = unchecked((uint)i * 0x9E3779B9u);
models[i][0] = (byte)value;
models[i][1] = (byte)(value >> 8);
models[i][2] = (byte)(value >> 16);
models[i][3] = (byte)(value >> 24);
}

return models;
}
Expand Down
53 changes: 53 additions & 0 deletions BitsKit.Benchmarks/GeneratedAccessorBenchmarkModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,57 @@ public partial struct GeneratedAccessorLsbModel
[BitField(5)]
[BitField("Value", 11)]
public uint BackingField;

[BitField(7)]
[BitField("SignedValue", 13)]
public int SignedBackingField;

[BitField(5)]
[BooleanField("Flag")]
public uint BooleanBackingField;

[BitField(3)]
[EnumField("Kind", 3, typeof(GeneratedAccessorKind))]
public uint EnumBackingField;

[BitField(9)]
[BitField("WideValue", 43)]
public ulong WideBackingField;

[BitField(5)]
[BitField("MostSignificantValue", 11, ReverseBitOrder = true)]
public uint MostSignificantBackingField;
}

public enum GeneratedAccessorKind : uint
{
Zero,
One,
Two,
Three,
Four,
Five,
Six,
Seven
}

[BitObject(BitOrder.LeastSignificant)]
public partial struct GeneratedAccessorMemoryModel
{
[BitField(5)]
[BitField("Value", 11, BitFieldType.UInt32)]
public Memory<byte> BackingField;
}

#if NET8_0_OR_GREATER

[BitObject(BitOrder.LeastSignificant)]
[System.Runtime.CompilerServices.InlineArray(4)]
public partial struct GeneratedAccessorInlineArrayModel
{
[BitField(5)]
[BitField("Value", 11, BitFieldType.UInt32, Modifiers = BitFieldModifiers.ReadOnly)]
private byte _element;
}

#endif
93 changes: 91 additions & 2 deletions BitsKit.Generator/Models/BitFieldModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,95 @@ BackingFieldType.Span or
_ => throw new NotSupportedException()
};

/// <summary>
/// Creates a specialized scalar read for fixed-width integral backing fields.
/// </summary>
protected bool TryGetDirectIntegralReadExpression(out string expression)
{
expression = string.Empty;

if (!TryGetDirectIntegralInfo(
out int workingWidth,
out string unsignedType))
{
return false;
}

string backingType = FieldType!.Value.ToString();
string unsignedSource = $"unchecked(({unsignedType}){{4}})";
if (BitOrder == BitOrder.MostSignificant)
{
string extracted =
$"(BinaryPrimitives.ReverseEndianness({unsignedSource}) << {BitOffset}) >> {workingWidth - BitCount}";

if (FieldType is BitFieldType.SByte or
BitFieldType.Int16 or
BitFieldType.Int32 or
BitFieldType.Int64)
{
string signedType = workingWidth == 64 ? "Int64" : "Int32";
int signShift = workingWidth - BitCount;
expression =
$"unchecked(({backingType})((unchecked(({signedType})({extracted})) << {signShift}) >> {signShift}))";
}
else
{
expression = $"unchecked(({backingType})({extracted}))";
}

return true;
}

if (FieldType is BitFieldType.SByte or
BitFieldType.Int16 or
BitFieldType.Int32 or
BitFieldType.Int64)
{
string signedType = workingWidth == 64 ? "Int64" : "Int32";
int leftShift = workingWidth - BitOffset - BitCount;
int rightShift = workingWidth - BitCount;
expression =
$"unchecked(({backingType})(unchecked(({signedType})({unsignedSource} << {leftShift})) >> {rightShift}))";
}
else
{
ulong valueMask = BitCount == 64 ? ulong.MaxValue : (1UL << BitCount) - 1;
string mask = FormatMask(valueMask, workingWidth);
expression =
$"unchecked(({backingType})(({unsignedSource} >> {BitOffset}) & {mask}))";
}

return true;
}

/// <summary>
/// Creates a specialized scalar bit test for integral backing fields.
/// </summary>
protected bool TryGetDirectIntegralBooleanReadExpression(out string expression)
{
expression = string.Empty;

if (!TryGetDirectIntegralInfo(
out int workingWidth,
out string unsignedType))
{
return false;
}

if (BitOrder == BitOrder.MostSignificant)
{
expression =
$"((BinaryPrimitives.ReverseEndianness(unchecked(({unsignedType}){{4}})) << {BitOffset}) >> {workingWidth - 1}) != 0";
}
else
{
string mask = FormatMask(1UL << BitOffset, workingWidth);
expression = $"(unchecked(({unsignedType}){{4}}) & {mask}) != 0";
}

return true;
}

/// <summary>
/// Creates a specialized scalar assignment for fixed-width integral backing fields.
/// </summary>
Expand All @@ -190,7 +279,8 @@ protected bool TryGetDirectIntegralWriteExpression(string valueExpression, out s

if (!TryGetDirectIntegralInfo(
out int workingWidth,
out string unsignedType))
out string unsignedType) ||
BitOrder != BitOrder.LeastSignificant)
{
return false;
}
Expand Down Expand Up @@ -223,7 +313,6 @@ private bool TryGetDirectIntegralInfo(
unsignedType = workingWidth == 64 ? "UInt64" : "UInt32";

return BackingFieldType == BackingFieldType.Integral &&
BitOrder == BitOrder.LeastSignificant &&
backingWidth != 0 &&
BitCount > 0 &&
BitOffset >= 0 &&
Expand Down
3 changes: 3 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 (TryGetDirectIntegralBooleanReadExpression(out string expression))
return "{0} {1} => " + expression + ";";

string template = BackingFieldType == BackingFieldType.Integral ?
StringConstants.BooleanGetterTemplate :
StringConstants.BooleanSpanGetterTemplate;
Expand Down
3 changes: 3 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 (TryGetDirectIntegralReadExpression(out string expression))
return $"{{0}} {{1}} => ({ReturnType})({expression});";

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

Expand Down
Loading
Loading