diff --git a/Spv.Generator/ConstantKey.cs b/Spv.Generator/ConstantKey.cs new file mode 100644 index 000000000..d3c1b905a --- /dev/null +++ b/Spv.Generator/ConstantKey.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Spv.Generator +{ + internal struct ConstantKey : IEquatable + { + 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); + } + } +} diff --git a/Spv.Generator/DeterministicHashCode.cs b/Spv.Generator/DeterministicHashCode.cs new file mode 100644 index 000000000..caba7ad32 --- /dev/null +++ b/Spv.Generator/DeterministicHashCode.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Spv.Generator +{ + /// + /// Similar to System.HashCode, but without introducing random values. + /// The same primes and shifts are used. + /// + 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(ReadOnlySpan 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 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 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 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; + } + } +} diff --git a/Spv.Generator/DeterministicStringKey.cs b/Spv.Generator/DeterministicStringKey.cs new file mode 100644 index 000000000..491bb745a --- /dev/null +++ b/Spv.Generator/DeterministicStringKey.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Spv.Generator +{ + internal class DeterministicStringKey : IEquatable + { + 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); + } + } +} diff --git a/Spv.Generator/Instruction.cs b/Spv.Generator/Instruction.cs index dfe28484d..5a1f98f14 100644 --- a/Spv.Generator/Instruction.cs +++ b/Spv.Generator/Instruction.cs @@ -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 _operands; + private InstructionOperands _operands; public uint Id { get; set; } @@ -22,7 +20,7 @@ namespace Spv.Generator Id = id; _resultType = resultType; - _operands = new List(); + _operands = new InstructionOperands(); } public void SetId(uint id) @@ -46,9 +44,10 @@ namespace Spv.Generator result += _resultType.WordCount; } - foreach (Operand operand in _operands) + Span 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 value) where T: struct + public void AddOperand(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 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 thisOperands = _operands.ToSpan(); + Span 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(_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(_operands.ToSpan())); } public bool Equals(Operand obj) diff --git a/Spv.Generator/InstructionOperands.cs b/Spv.Generator/InstructionOperands.cs new file mode 100644 index 000000000..3e53e60e7 --- /dev/null +++ b/Spv.Generator/InstructionOperands.cs @@ -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 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; + } + } + } +} diff --git a/Spv.Generator/LiteralInteger.cs b/Spv.Generator/LiteralInteger.cs index b9afa0055..d88ee5f70 100644 --- a/Spv.Generator/LiteralInteger.cs +++ b/Spv.Generator/LiteralInteger.cs @@ -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 value) where T : struct + public static LiteralInteger CreateForEnum(T value) where T : Enum { - return Create(value, IntegerType.Int32); + return value; } - private static LiteralInteger Create(T value, IntegerType integerType) where T: struct - { - return new LiteralInteger(MemoryMarshal.Cast(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) diff --git a/Spv.Generator/LiteralString.cs b/Spv.Generator/LiteralString.cs index 259b946ac..1cb1b8383 100644 --- a/Spv.Generator/LiteralString.cs +++ b/Spv.Generator/LiteralString.cs @@ -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) diff --git a/Spv.Generator/Module.cs b/Spv.Generator/Module.cs index 03d649680..5e678af36 100644 --- a/Spv.Generator/Module.cs +++ b/Spv.Generator/Module.cs @@ -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 _capabilities; private List _extensions; - private List _extInstImports; + private Dictionary _extInstImports; private AddressingModel _addressingModel; private MemoryModel _memoryModel; @@ -29,9 +27,11 @@ namespace Spv.Generator private List _annotations; // In the declaration block. - private List _typeDeclarations; + private Dictionary _typeDeclarations; // In the declaration block. private List _globals; + // In the declaration block. + private Dictionary _constants; // In the declaration block, for function that aren't defined in the module. private List _functionsDeclarations; @@ -43,14 +43,15 @@ namespace Spv.Generator _bound = 1; _capabilities = new List(); _extensions = new List(); - _extInstImports = new List(); + _extInstImports = new Dictionary(); _addressingModel = AddressingModel.Logical; _memoryModel = MemoryModel.Simple; _entrypoints = new List(); _executionModes = new List(); _debug = new List(); _annotations = new List(); - _typeDeclarations = new List(); + _typeDeclarations = new Dictionary(); + _constants = new Dictionary(); _globals = new List(); _functionsDeclarations = new List(); _functionsDefinitions = new List(); @@ -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 declarations = new List(); - 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. diff --git a/Spv.Generator/TypeDeclarationKey.cs b/Spv.Generator/TypeDeclarationKey.cs new file mode 100644 index 000000000..a4aa95634 --- /dev/null +++ b/Spv.Generator/TypeDeclarationKey.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Spv.Generator +{ + internal struct TypeDeclarationKey : IEquatable + { + 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); + } + } +}