Some optimizations to Spv.Generator

- Dictionary for lookups of type declarations, constants, extinst
- LiteralInteger internal data format -> ushort
- Deterministic HashCode implementation to avoid spirv result not being the same between runs
- Inline operand list instead of List<T>, falls back to array if many operands. (large performance boost)

TODO: improve instruction allocation, structured program creator, ssa?
This commit is contained in:
riperiperi 2022-02-16 13:24:29 +00:00
parent 12dec18f39
commit bf94f4c7d6
9 changed files with 356 additions and 73 deletions

View file

@ -0,0 +1,30 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Spv.Generator
{
internal struct ConstantKey : IEquatable<ConstantKey>
{
private Instruction _constant;
public ConstantKey(Instruction constant)
{
_constant = constant;
}
public override int GetHashCode()
{
return HashCode.Combine(_constant.Opcode, _constant.GetHashCodeContent(), _constant.GetHashCodeResultType());
}
public bool Equals(ConstantKey other)
{
return _constant.Opcode == other._constant.Opcode && _constant.EqualsContent(other._constant) && _constant.EqualsResultType(other._constant);
}
public override bool Equals([NotNullWhen(true)] object obj)
{
return obj is ConstantKey && Equals((ConstantKey)obj);
}
}
}

View file

@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Spv.Generator
{
/// <summary>
/// Similar to System.HashCode, but without introducing random values.
/// The same primes and shifts are used.
/// </summary>
internal static class DeterministicHashCode
{
private const uint Prime1 = 2654435761U;
private const uint Prime2 = 2246822519U;
private const uint Prime3 = 3266489917U;
private const uint Prime4 = 668265263U;
public static int GetHashCode(string value)
{
uint hash = (uint)value.Length + Prime1;
for (int i = 0; i < value.Length; i++)
{
hash += (hash << 7) ^ value[i];
}
return (int)MixFinal(hash);
}
public static int Combine<T>(ReadOnlySpan<T> values)
{
uint hashCode = Prime2;
hashCode += 4 * (uint)values.Length;
foreach (T value in values)
{
uint hc = (uint)(value?.GetHashCode() ?? 0);
hashCode = MixStep(hashCode, hc);
}
return (int)MixFinal(hashCode);
}
public static int Combine<T1, T2>(T1 value1, T2 value2)
{
uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
uint hash = Prime2;
hash += 8;
hash = MixStep(hash, hc1);
hash = MixStep(hash, hc2);
return (int)MixFinal(hash);
}
public static int Combine<T1, T2, T3>(T1 value1, T2 value2, T3 value3)
{
uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
uint hash = Prime2;
hash += 12;
hash = MixStep(hash, hc1);
hash = MixStep(hash, hc2);
hash = MixStep(hash, hc3);
return (int)MixFinal(hash);
}
public static int Combine<T1, T2, T3, T4>(T1 value1, T2 value2, T3 value3, T4 value4)
{
uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
uint hc4 = (uint)(value4?.GetHashCode() ?? 0);
uint hash = Prime2;
hash += 16;
hash = MixStep(hash, hc1);
hash = MixStep(hash, hc2);
hash = MixStep(hash, hc3);
hash = MixStep(hash, hc4);
return (int)MixFinal(hash);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint MixStep(uint hashCode, uint mixValue)
{
return BitOperations.RotateLeft(hashCode + mixValue * Prime3, 17) * Prime4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint MixFinal(uint hash)
{
hash ^= hash >> 15;
hash *= Prime2;
hash ^= hash >> 13;
hash *= Prime3;
hash ^= hash >> 16;
return hash;
}
}
}

View file

@ -0,0 +1,30 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Spv.Generator
{
internal class DeterministicStringKey : IEquatable<DeterministicStringKey>
{
private string _value;
public DeterministicStringKey(string value)
{
_value = value;
}
public override int GetHashCode()
{
return DeterministicHashCode.GetHashCode(_value);
}
public bool Equals(DeterministicStringKey other)
{
return _value == other._value;
}
public override bool Equals([NotNullWhen(true)] object obj)
{
return obj is DeterministicStringKey && Equals((DeterministicStringKey)obj);
}
}
}

View file

@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
namespace Spv.Generator
{
@ -12,7 +10,7 @@ namespace Spv.Generator
public Specification.Op Opcode { get; private set; }
private Instruction _resultType;
public List<Operand> _operands;
private InstructionOperands _operands;
public uint Id { get; set; }
@ -22,7 +20,7 @@ namespace Spv.Generator
Id = id;
_resultType = resultType;
_operands = new List<Operand>();
_operands = new InstructionOperands();
}
public void SetId(uint id)
@ -46,9 +44,10 @@ namespace Spv.Generator
result += _resultType.WordCount;
}
foreach (Operand operand in _operands)
Span<Operand> operands = _operands.ToSpan();
for (int i = 0; i < operands.Length; i++)
{
result += operand.WordCount;
result += operands[i].WordCount;
}
return result;
@ -101,7 +100,7 @@ namespace Spv.Generator
AddOperand(new LiteralString(value));
}
public void AddOperand<T>(T value) where T: struct
public void AddOperand<T>(T value) where T: Enum
{
if (!typeof(T).IsPrimitive && !typeof(T).IsEnum)
{
@ -124,9 +123,10 @@ namespace Spv.Generator
writer.Write(Id);
}
foreach (Operand operand in _operands)
Span<Operand> operands = _operands.ToSpan();
for (int i = 0; i < operands.Length; i++)
{
operand.WriteOperand(writer);
operands[i].WriteOperand(writer);
}
}
@ -188,7 +188,23 @@ namespace Spv.Generator
public bool EqualsContent(Instruction cmpObj)
{
return _operands.SequenceEqual(cmpObj._operands);
Span<Operand> thisOperands = _operands.ToSpan();
Span<Operand> cmpOperands = cmpObj._operands.ToSpan();
if (thisOperands.Length != cmpOperands.Length)
{
return false;
}
for (int i = 0; i < thisOperands.Length; i++)
{
if (!thisOperands[i].Equals(cmpOperands[i]))
{
return false;
}
}
return true;
}
public bool EqualsResultType(Instruction cmpObj)
@ -196,9 +212,19 @@ namespace Spv.Generator
return _resultType.Opcode == cmpObj._resultType.Opcode && _resultType.EqualsContent(cmpObj._resultType);
}
public int GetHashCodeContent()
{
return DeterministicHashCode.Combine<Operand>(_operands.ToSpan());
}
public int GetHashCodeResultType()
{
return DeterministicHashCode.Combine(_resultType.Opcode, _resultType.GetHashCodeContent());
}
public override int GetHashCode()
{
return HashCode.Combine(Opcode, Id, _resultType, _operands);
return DeterministicHashCode.Combine(Opcode, Id, _resultType, DeterministicHashCode.Combine<Operand>(_operands.ToSpan()));
}
public bool Equals(Operand obj)

View file

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Spv.Generator
{
public struct InstructionOperands
{
private const int InternalCount = 5;
public int Count;
public Operand Operand1;
public Operand Operand2;
public Operand Operand3;
public Operand Operand4;
public Operand Operand5;
public Operand[] Overflow;
public Span<Operand> ToSpan()
{
if (Count > InternalCount)
{
return MemoryMarshal.CreateSpan(ref this.Overflow[0], Count);
}
else
{
return MemoryMarshal.CreateSpan(ref this.Operand1, Count);
}
}
public void Add(Operand operand)
{
if (Count < InternalCount)
{
MemoryMarshal.CreateSpan(ref this.Operand1, Count + 1)[Count] = operand;
Count++;
}
else
{
if (Overflow == null)
{
Overflow = new Operand[InternalCount * 2];
MemoryMarshal.CreateSpan(ref this.Operand1, InternalCount).CopyTo(Overflow.AsSpan());
}
else if (Count == Overflow.Length)
{
Array.Resize(ref Overflow, Overflow.Length * 2);
}
Overflow[Count++] = operand;
}
}
}
}

View file

@ -1,7 +1,5 @@
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace Spv.Generator
{
@ -20,42 +18,44 @@ namespace Spv.Generator
}
private IntegerType _integerType;
private ulong _data;
private byte[] _data;
public ushort WordCount { get; }
private LiteralInteger(byte[] data, IntegerType integerType)
private LiteralInteger(ulong data, IntegerType integerType, ushort wordCount)
{
_data = data;
_integerType = integerType;
WordCount = wordCount;
}
public static implicit operator LiteralInteger(int value) => Create(value, IntegerType.Int32);
public static implicit operator LiteralInteger(uint value) => Create(value, IntegerType.UInt32);
public static implicit operator LiteralInteger(long value) => Create(value, IntegerType.Int64);
public static implicit operator LiteralInteger(ulong value) => Create(value, IntegerType.UInt64);
public static implicit operator LiteralInteger(float value) => Create(value, IntegerType.Float32);
public static implicit operator LiteralInteger(double value) => Create(value, IntegerType.Float64);
public static implicit operator LiteralInteger(Enum value) => Create((int)Convert.ChangeType(value, typeof(int)), IntegerType.Int32);
public static implicit operator LiteralInteger(int value) => new LiteralInteger((ulong)value, IntegerType.Int32, 1);
public static implicit operator LiteralInteger(uint value) => new LiteralInteger(value, IntegerType.UInt32, 1);
public static implicit operator LiteralInteger(long value) => new LiteralInteger((ulong)value, IntegerType.Int64, 2);
public static implicit operator LiteralInteger(ulong value) => new LiteralInteger(value, IntegerType.UInt64, 2);
public static implicit operator LiteralInteger(float value) => new LiteralInteger(BitConverter.SingleToUInt32Bits(value), IntegerType.Float32, 1);
public static implicit operator LiteralInteger(double value) => new LiteralInteger(BitConverter.DoubleToUInt64Bits(value), IntegerType.Float64, 2);
public static implicit operator LiteralInteger(Enum value) => new LiteralInteger((ulong)Convert.ChangeType(value, typeof(ulong)), IntegerType.Int32, 1);
// NOTE: this is not in the standard, but this is some syntax sugar useful in some instructions (TypeInt ect)
public static implicit operator LiteralInteger(bool value) => Create(Convert.ToInt32(value), IntegerType.Int32);
public static implicit operator LiteralInteger(bool value) => new LiteralInteger(Convert.ToUInt64(value), IntegerType.Int32, 1);
public static LiteralInteger CreateForEnum<T>(T value) where T : struct
public static LiteralInteger CreateForEnum<T>(T value) where T : Enum
{
return Create(value, IntegerType.Int32);
return value;
}
private static LiteralInteger Create<T>(T value, IntegerType integerType) where T: struct
{
return new LiteralInteger(MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)).ToArray(), integerType);
}
public ushort WordCount => (ushort)(_data.Length / 4);
public void WriteOperand(BinaryWriter writer)
{
writer.Write(_data);
if (WordCount == 1)
{
writer.Write((uint)_data);
}
else
{
writer.Write(_data);
}
}
public override bool Equals(object obj)
@ -65,12 +65,12 @@ namespace Spv.Generator
public bool Equals(LiteralInteger cmpObj)
{
return Type == cmpObj.Type && _integerType == cmpObj._integerType && _data.SequenceEqual(cmpObj._data);
return Type == cmpObj.Type && _integerType == cmpObj._integerType && _data == cmpObj._data;
}
public override int GetHashCode()
{
return HashCode.Combine(Type, _data);
return DeterministicHashCode.Combine(Type, _data);
}
public bool Equals(Operand obj)

View file

@ -40,7 +40,7 @@ namespace Spv.Generator
public override int GetHashCode()
{
return HashCode.Combine(Type, _value);
return DeterministicHashCode.Combine(Type, DeterministicHashCode.GetHashCode(_value));
}
public bool Equals(Operand obj)

View file

@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using static Spv.Specification;
namespace Spv.Generator
@ -19,7 +17,7 @@ namespace Spv.Generator
// Follow spec order here why keeping it as dumb as possible.
private List<Capability> _capabilities;
private List<string> _extensions;
private List<Instruction> _extInstImports;
private Dictionary<DeterministicStringKey, Instruction> _extInstImports;
private AddressingModel _addressingModel;
private MemoryModel _memoryModel;
@ -29,9 +27,11 @@ namespace Spv.Generator
private List<Instruction> _annotations;
// In the declaration block.
private List<Instruction> _typeDeclarations;
private Dictionary<TypeDeclarationKey, Instruction> _typeDeclarations;
// In the declaration block.
private List<Instruction> _globals;
// In the declaration block.
private Dictionary<ConstantKey, Instruction> _constants;
// In the declaration block, for function that aren't defined in the module.
private List<Instruction> _functionsDeclarations;
@ -43,14 +43,15 @@ namespace Spv.Generator
_bound = 1;
_capabilities = new List<Capability>();
_extensions = new List<string>();
_extInstImports = new List<Instruction>();
_extInstImports = new Dictionary<DeterministicStringKey, Instruction>();
_addressingModel = AddressingModel.Logical;
_memoryModel = MemoryModel.Simple;
_entrypoints = new List<Instruction>();
_executionModes = new List<Instruction>();
_debug = new List<Instruction>();
_annotations = new List<Instruction>();
_typeDeclarations = new List<Instruction>();
_typeDeclarations = new Dictionary<TypeDeclarationKey, Instruction>();
_constants = new Dictionary<ConstantKey, Instruction>();
_globals = new List<Instruction>();
_functionsDeclarations = new List<Instruction>();
_functionsDefinitions = new List<Instruction>();
@ -73,44 +74,43 @@ namespace Spv.Generator
public Instruction AddExtInstImport(string import)
{
var key = new DeterministicStringKey(import);
if (_extInstImports.TryGetValue(key, out Instruction extInstImport))
{
// update the duplicate instance to use the good id so it ends up being encoded right.
return extInstImport;
}
Instruction instruction = new Instruction(Op.OpExtInstImport);
instruction.AddOperand(import);
foreach (Instruction extInstImport in _extInstImports)
{
if (extInstImport.Opcode == Op.OpExtInstImport && extInstImport.EqualsContent(instruction))
{
// update the duplicate instance to use the good id so it ends up being encoded right.
return extInstImport;
}
}
instruction.SetId(GetNewId());
_extInstImports.Add(instruction);
_extInstImports.Add(key, instruction);
return instruction;
}
private void AddTypeDeclaration(Instruction instruction, bool forceIdAllocation)
{
var key = new TypeDeclarationKey(instruction);
if (!forceIdAllocation)
{
foreach (Instruction typeDeclaration in _typeDeclarations)
if (_typeDeclarations.TryGetValue(key, out Instruction typeDeclaration))
{
if (typeDeclaration.Opcode == instruction.Opcode && typeDeclaration.EqualsContent(instruction))
{
// update the duplicate instance to use the good id so it ends up being encoded right.
instruction.SetId(typeDeclaration.Id);
// update the duplicate instance to use the good id so it ends up being encoded right.
return;
}
instruction.SetId(typeDeclaration.Id);
return;
}
}
instruction.SetId(GetNewId());
_typeDeclarations.Add(instruction);
_typeDeclarations.Add(key, instruction);
}
public void AddEntryPoint(ExecutionModel executionModel, Instruction function, string name, params Instruction[] interfaces)
@ -195,20 +195,19 @@ namespace Spv.Generator
constant.Opcode == Op.OpConstantNull ||
constant.Opcode == Op.OpConstantComposite);
foreach (Instruction global in _globals)
{
if (global.Opcode == constant.Opcode && global.EqualsContent(constant) && global.EqualsResultType(constant))
{
// update the duplicate instance to use the good id so it ends up being encoded right.
constant.SetId(global.Id);
var key = new ConstantKey(constant);
return;
}
if (_constants.TryGetValue(key, out Instruction global))
{
// update the duplicate instance to use the good id so it ends up being encoded right.
constant.SetId(global.Id);
return;
}
constant.SetId(GetNewId());
_globals.Add(constant);
_constants.Add(key, constant);
}
public Instruction ExtInst(Instruction resultType, Instruction set, LiteralInteger instruction, params Operand[] parameters)
@ -275,7 +274,7 @@ namespace Spv.Generator
}
// 3.
foreach (Instruction extInstImport in _extInstImports)
foreach (Instruction extInstImport in _extInstImports.Values)
{
extInstImport.Write(writer);
}
@ -313,8 +312,9 @@ namespace Spv.Generator
// Ensure that everything is in the right order in the declarations section
List<Instruction> declarations = new List<Instruction>();
declarations.AddRange(_typeDeclarations);
declarations.AddRange(_typeDeclarations.Values);
declarations.AddRange(_globals);
declarations.AddRange(_constants.Values);
declarations.Sort((Instruction x, Instruction y) => x.Id.CompareTo(y.Id));
// 9.

View file

@ -0,0 +1,30 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Spv.Generator
{
internal struct TypeDeclarationKey : IEquatable<TypeDeclarationKey>
{
private Instruction _typeDeclaration;
public TypeDeclarationKey(Instruction typeDeclaration)
{
_typeDeclaration = typeDeclaration;
}
public override int GetHashCode()
{
return DeterministicHashCode.Combine(_typeDeclaration.Opcode, _typeDeclaration.GetHashCodeContent());
}
public bool Equals(TypeDeclarationKey other)
{
return _typeDeclaration.Opcode == other._typeDeclaration.Opcode && _typeDeclaration.EqualsContent(other._typeDeclaration);
}
public override bool Equals([NotNullWhen(true)] object obj)
{
return obj is TypeDeclarationKey && Equals((TypeDeclarationKey)obj);
}
}
}