File: Internal\Metadata\NativeFormat\Writer\NativeMetadataWriter.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.MetadataTransform\ILCompiler.MetadataTransform.csproj (ILCompiler.MetadataTransform)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using Debug = System.Diagnostics.Debug;
using ConditionalAttribute = System.Diagnostics.ConditionalAttribute;
using Internal.LowLevelLinq;
using Internal.NativeFormat;
using Graph = Internal.Metadata.NativeFormat.Writer.AdjacencyGraph;
using System.Runtime.InteropServices;

namespace Internal.Metadata.NativeFormat.Writer
{
    internal sealed class Edge
    {
        public MetadataRecord Source;
        public MetadataRecord Target;
        public Edge(MetadataRecord source, MetadataRecord target)
        {
            Source = source;
            Target = target;
        }
    };
    internal sealed class AdjacencyGraph
    {
        private HashSet<MetadataRecord> _vertices = new HashSet<MetadataRecord>();
        // private Dictionary<MetadataRecord, HashSet<Edge>> _edges = new Dictionary<MetadataRecord, HashSet<Edge>>();

        public void AddVertex(MetadataRecord v)
        {
            _vertices.Add(v);
        }

#if false
        public void AddEdge(Edge e)
        {
            HashSet<Edge> vedges;
            if (!_edges.TryGetValue(e.Source, out vedges))
            {
                vedges = new HashSet<Edge>();
                _edges.Add(e.Source, vedges);
            }
            vedges.Add(e);
        }
#endif

        public bool ContainsVertex(MetadataRecord v)
        {
            return _vertices.Contains(v);
        }

        public IEnumerable<MetadataRecord> Vertices
        { get { return _vertices; } }
    }

    internal partial interface IRecordVisitor
    {
        // Adds edge
        DstT Visit<SrcT, DstT>(SrcT src, DstT dst)
            where SrcT : MetadataRecord
            where DstT : MetadataRecord;

        // Adds grouped edges
        Dictionary<string, DstT> Visit<SrcT, DstT>(SrcT src, IEnumerable<KeyValuePair<string, DstT>> dst)
            where SrcT : MetadataRecord
            where DstT : MetadataRecord;

        // Adds grouped edges
        List<DstT> Visit<SrcT, DstT>(SrcT src, List<DstT> dst)
            where SrcT : MetadataRecord
            where DstT : MetadataRecord;
    }

    internal sealed class SourceVertex : MetadataRecord
    {
        public override HandleType HandleType
        {
            get { throw new NotImplementedException(); }
        }

        internal override void Save(NativeWriter writer)
        {
            throw new NotImplementedException();
        }

        internal override void Visit(IRecordVisitor visitor)
        {
            throw new NotImplementedException();
        }
    }

    internal abstract class RecordVisitorBase : IRecordVisitor
    {
#if false
        private class SequenceComparer<T>
            : IEqualityComparer<IEnumerable<T>>
        {
            public bool Equals(IEnumerable<T> x, IEnumerable<T> y)
            {
                return
                    Object.ReferenceEquals(x, y) ||
                    (x != null &&
                     y != null &&
                     x.GetType() == y.GetType() &&
                     x.SequenceEqual(y));
            }

            public static int GetHashCode(T o)
            {
                if (o == null)
                    return 0;
                return o.GetHashCode();
            }

            public int GetHashCode(IEnumerable<T> obj)
            {
                if (obj == null)
                    return 0;
                return obj.Aggregate(0, (h, o) => h ^ GetHashCode(o));
            }
        }

        Dictionary<IEnumerable<MetadataRecord>, object> _listPool = new Dictionary<IEnumerable<MetadataRecord>, object>(new SequenceComparer<MetadataRecord>());

        private List<T> GetPooledArray<T>(List<T> rec) where T : MetadataRecord
        {
            if (rec == null || rec.Count() == 0)
                return rec;

            object pooledRecord;
            if (_listPool.TryGetValue(rec, out pooledRecord) && pooledRecord != rec)
            {
                if (rec.GetType().GetElementType() == typeof(MetadataRecord))
                    _stats.ArraySizeSavings += rec.Count() * sizeof(int) - 3;
                else
                    _stats.ArraySizeSavings += rec.Count() * 3 - 3;
                Debug.Assert(rec.GetType() == pooledRecord.GetType());
                rec = (List<T>)pooledRecord;
            }
            else
            {
                _listPool[rec] = rec;
            }
            return rec;
        }

        private struct Stats
        {
            public int ArraySizeSavings;
        }
        private Stats _stats = new Stats();
#endif

        private Dictionary<MetadataRecord, MetadataRecord> _recordPool = new Dictionary<MetadataRecord, MetadataRecord>();

        public RecordVisitorBase()
        {
            _graph.AddVertex(MetaSourceVertex);
        }

        internal T MapToPooledRecord<T>(T rec) where T : MetadataRecord
        {
            return (T)_recordPool[rec];
        }

        private T GetPooledRecord<T>(T rec) where T : MetadataRecord
        {
            if (rec == null)
                return rec;

            MetadataRecord pooledRecord;
            if (_recordPool.TryGetValue(rec, out pooledRecord) && pooledRecord != rec)
            {
                Debug.Assert(rec.GetType() == pooledRecord.GetType());
                rec = (T)pooledRecord;
            }
            else
            {
                _recordPool[rec] = rec;
            }
            return rec;
        }

        // Adds a Vertex
        public T Visit<T>(T rec)
            where T : MetadataRecord
        {
            rec = GetPooledRecord(rec);

            if (rec == null)
                return rec;

            if (_graph.ContainsVertex(rec))
                return rec;

            _graph.AddVertex(rec);
            _queue.Enqueue(rec);

            return rec;
        }

        // Adds Edges
        public Dictionary<string, DstT> Visit<SrcT, DstT>(SrcT src, IEnumerable<KeyValuePair<string, DstT>> dst)
            where SrcT : MetadataRecord
            where DstT : MetadataRecord
        {
            var res = new Dictionary<string, DstT>();

            foreach (var kv in dst)
            {
                res.Add(kv.Key, Visit(src, kv.Value, true));
            }

            return res;
        }

        public void Run(IEnumerable<MetadataRecord> records)
        {
            foreach (var rec in records)
            {
                Visit((MetadataRecord)null, rec);
            }

            while (_queue.Count != 0)
            {
                _queue.Dequeue().Visit(this);
            }
        }

        // Adds Edges
        public List<DstT> Visit<SrcT, DstT>(SrcT src, List<DstT> dst)
            where SrcT : MetadataRecord
            where DstT : MetadataRecord
        {
#if false
            return GetPooledArray(dst.Select(d => Visit(src, d, true)).ToList());
#else
            var result = new List<DstT>(dst.Count);
            foreach (var destNode in dst)
                result.Add(Visit(src, destNode, true));

            return result;
#endif
        }

        // Adds Edge
        public DstT Visit<SrcT, DstT>(SrcT src, DstT dst)
            where SrcT : MetadataRecord
            where DstT : MetadataRecord
        {
            return Visit(src, dst, src == null);
        }

        // Adds Edge
        internal DstT Visit<SrcT, DstT>(SrcT src, DstT dst, bool isChild)
            where SrcT : MetadataRecord
            where DstT : MetadataRecord
        {
            var res = Visit(dst);

#if false
            if (res != null)
            {
                _graph.AddEdge(new Edge(src ?? MetaSourceVertex, res));
            }
#endif

            return res;
        }

        protected Queue<MetadataRecord> _queue = new Queue<MetadataRecord>();
        protected Graph _graph = new Graph();

        public Graph Graph { get { return _graph; } }
        public readonly MetadataRecord MetaSourceVertex = new SourceVertex();
    }

    internal sealed class RecordVisitor : RecordVisitorBase
    {
    }


    internal sealed partial class MetadataHeader : MetadataRecord
    {
        public const uint Signature = 0xDEADDFFD;
        public List<ScopeDefinition> ScopeDefinitions = new List<ScopeDefinition>();

        internal override void Save(NativeWriter writer)
        {
            writer.WriteUInt32(Signature);
            writer.Write(ScopeDefinitions);
        }

        public override HandleType HandleType
        {
            get { throw new NotImplementedException(); }
        }

        internal override void Visit(IRecordVisitor visitor)
        {
            ScopeDefinitions = visitor.Visit(this, ScopeDefinitions);
        }
    }

    public partial class MetadataWriter
    {
        internal MetadataHeader _metadataHeader = new MetadataHeader();

        public List<MetadataRecord> AdditionalRootRecords { get; private set; }

        public List<ScopeDefinition> ScopeDefinitions
        {
            get { return _metadataHeader.ScopeDefinitions; }
        }

        private RecordVisitor _visitor;

        public MetadataWriter()
        {
            AdditionalRootRecords = new List<MetadataRecord>();
        }

        public int GetRecordHandle(MetadataRecord rec)
        {
            var realRec = _visitor.MapToPooledRecord(rec);

            Debug.Assert(realRec.Handle.Offset != 0);

            return realRec.Handle._value;
        }

        public void Write(Stream stream)
        {
            _visitor = new RecordVisitor();

            _visitor.Run(ScopeDefinitions);
            _visitor.Run(AdditionalRootRecords);

            IEnumerable<MetadataRecord> records = _visitor.Graph.Vertices.Where(v => v != _visitor.MetaSourceVertex);

            var writer = new NativeWriter();

            var section = writer.NewSection();

            _metadataHeader.ScopeDefinitions = ScopeDefinitions;
            section.Place(_metadataHeader);

            foreach (var rec in records)
            {
                section.Place(rec);
            }

            writer.Save(stream);

            if (LogWriter != null)
            {
                // Create a CSV file, one line per meta-data record.
                LogWriter.WriteLine("Handle, Kind, Name, Children");
                // needed to enumerate children of a meta-data record
                var childVisitor = new WriteChildrenVisitor(LogWriter);

                foreach (var rec in records)
                {
                    // First the metadata handle
                    LogWriter.Write(rec.Handle._value.ToString("x8"));
                    LogWriter.Write(", ");

                    // Next the handle type
                    LogWriter.Write(rec.HandleType.ToString());
                    LogWriter.Write(", ");

                    // 3rd, the name, Quote the string if not already quoted
                    string asString = rec.ToString(false);
                    bool alreadyQuoted = asString.StartsWith('\"') && asString.EndsWith('\"');
                    if (!alreadyQuoted)
                    {
                        LogWriter.Write("\"");
                        asString = asString.Replace("\\", "\\\\").Replace("\"", "\\\"");  // Quote " and \
                    }
                    // TODO we assume that a quoted string is escaped properly
                    LogWriter.Write(asString);

                    if (!alreadyQuoted)
                        LogWriter.Write("\"");
                    LogWriter.Write(", ");

                    // Finally write out the handle IDs for my children
                    LogWriter.Write("\"");
                    childVisitor.Reset();
                    rec.Visit(childVisitor);
                    LogWriter.Write("\"");
                    LogWriter.WriteLine();
                }
                LogWriter.Flush();
            }
        }

        // WriteChildrenVisitor is a helper class needed to write out the list of the
        // handles (as space separated hex numbers) of all children of a given node
        // to the 'logWriter' text stream.  It simply implements the IRecordVisitor
        // interface to hook the callbacks needed for the MetadataRecord.Visit API.
        // It is only used in the Write() method above.
        private sealed class WriteChildrenVisitor : IRecordVisitor
        {
            public WriteChildrenVisitor(TextWriter logWriter)
            {
                _logWriter = logWriter;
            }

            // Resets the state back to what is was just after the constructor is called.
            public void Reset() { _notFirst = false; }

            // All visits come to here for every child.  Here we simply print the handle as hex.
            public void Log(MetadataRecord rec)
            {
                if (rec == null)
                    return;
                if (_notFirst)
                    _logWriter.Write(" ");
                else
                    _notFirst = true;
                _logWriter.Write(rec.Handle._value.ToString("x"));
            }

            public DstT Visit<SrcT, DstT>(SrcT src, DstT dst) where SrcT : MetadataRecord where DstT : MetadataRecord
            {
                Log(dst);
                return dst;
            }
            public Dictionary<string, DstT> Visit<SrcT, DstT>(SrcT src, IEnumerable<KeyValuePair<string, DstT>> dst) where SrcT : MetadataRecord where DstT : MetadataRecord
            {
                foreach (var keyValue in dst)
                    Log(keyValue.Value);
                return dst as Dictionary<string, DstT>;
            }
            public List<DstT> Visit<SrcT, DstT>(SrcT src, List<DstT> dst) where SrcT : MetadataRecord where DstT : MetadataRecord
            {
                foreach (var elem in dst)
                    Log(elem);
                return dst;
            }

            private bool _notFirst;           // The first child should not have a space before it.  This tracks this
            private TextWriter _logWriter;    // Where we write output to
        }

        public TextWriter LogWriter;
    }

    internal sealed class ReentrancyGuardStack
    {
        private MetadataRecord[] _array;
        private int _size;

        public ReentrancyGuardStack()
        {
            // Start with a non-zero initial size. With a bit of luck this will prevent memory allocations
            // when Push() is used.
            _array = new MetadataRecord[8];
            _size = 0;
        }

        public bool Contains(MetadataRecord item)
        {
            int count = _size;
            while (count-- > 0)
            {
                // Important: we use ReferenceEquals because this method will be called from Equals()
                // on 'record'. This is also why we can't use System.Collections.Generic.Stack.
                if (ReferenceEquals(item, _array[count]))
                    return true;
            }
            return false;
        }

        public MetadataRecord Pop()
        {
            if (_size == 0)
                throw new InvalidOperationException();
            MetadataRecord record = _array[--_size];
            _array[_size] = null;
            return record;
        }

        public void Push(MetadataRecord item)
        {
            if (_size == _array.Length)
                Array.Resize(ref _array, 2 * _array.Length);
            _array[_size++] = item;
        }
    }

    public abstract class MetadataRecord : Vertex
    {
        protected int _hash;

        // debug-only guard against reentrancy in GetHashCode()
        private bool _gettingHashCode;

        [Conditional("DEBUG")]
        protected void EnterGetHashCode()
        {
            Debug.Assert(!_gettingHashCode);
            _gettingHashCode = true;
        }

        [Conditional("DEBUG")]
        protected void LeaveGetHashCode()
        {
            Debug.Assert(_gettingHashCode);
            _gettingHashCode = false;
        }

        public abstract HandleType HandleType { get; }

        internal int HandleOffset
        {
            get
            {
                return _offset & 0x01FFFFFF;
            }
        }

        internal Handle Handle
        {
            get
            {
                return new Handle(HandleType, HandleOffset);
            }
        }

        internal abstract void Visit(IRecordVisitor visitor);

        public override string ToString()
        {
            return "[@TODO:" + this.GetType().ToString() + "]";
        }

        public virtual string ToString(bool includeHandleValue)
        {
            return ToString();
        }

        protected static string ToString<T>(IEnumerable<T> arr, string sep = ", ", bool includeHandleValue = false) where T : MetadataRecord
        {
            return string.Join(sep, arr.Select(v => v.ToString(includeHandleValue)));
        }
    }

    /// <summary>
    /// Supplements generated class with convenient coversion operators
    /// </summary>
    public partial class ConstantStringValue
    {
        public static explicit operator string(ConstantStringValue value)
        {
            if (value == null)
                return null;
            else
                return value.Value;
        }

        public static explicit operator ConstantStringValue(string value)
        {
            return new ConstantStringValue() { Value = value };
        }
    }

    public partial class ScopeDefinition
    {
        public override string ToString()
        {
            return ToString(true);
        }
        public override string ToString(bool includeHandleValue)
        {
            return Name.ToString() + (includeHandleValue ? string.Format(" ({0:x})", Handle._value) : "");
        }
    }

    public partial class ScopeReference
    {
        public override string ToString()
        {
            return ToString(true);
        }
        public override string ToString(bool includeHandleValue)
        {
            return Name.ToString() + (includeHandleValue ? string.Format(" ({0:x})", Handle._value) : "");
        }
    }

    public partial class NamespaceDefinition
    {
        public override string ToString()
        {
            return ToString(true);
        }

        public override string ToString(bool includeHandleValue)
        {
            string str;

            if (Name != null && !string.IsNullOrEmpty(Name.Value))
            {
                str = Name.Value;
            }
            else
            {
                str = string.Empty;
            }

            if (includeHandleValue)
                str += string.Format("({0})", Handle.ToString());

            if (this.ParentScopeOrNamespace != null)
            {
                var pns = this.ParentScopeOrNamespace as NamespaceDefinition;
                if (pns != null)
                {
                    if (!string.IsNullOrEmpty(pns.ToString(false)))
                        str = pns.ToString(false) + '.' + str;
                }
            }
            return str;
        }
    }

    public partial class NamespaceReference
    {
        public override string ToString()
        {
            return ToString(true);
        }
        public override string ToString(bool includeHandleValue)
        {
            string str;

            if (Name != null && !string.IsNullOrEmpty(Name.Value))
            {
                str = Name.Value;
            }
            else
            {
                str = string.Empty;
            }

            if (includeHandleValue)
                str += string.Format("({0})", Handle.ToString());

            if (this.ParentScopeOrNamespace != null)
            {
                var pns = this.ParentScopeOrNamespace as NamespaceReference;
                if (pns != null)
                {
                    if (!string.IsNullOrEmpty(pns.ToString(false)))
                        str = pns.ToString(false) + '.' + str;
                }
                else
                {
                    //str = ParentScopeOrNamespace.ToString() + " : " + str;
                }
            }
            return str;
        }
    }

    public partial class TypeDefinition
    {
        public override string ToString()
        {
            return ToString(false);
        }
        public override string ToString(bool includeHandleValue)
        {
            string str;
            if (this.EnclosingType != null)
            {
                str = this.EnclosingType.ToString(false) + "+" + Name.Value;
                if (includeHandleValue)
                    str += string.Format(" ({0:x})", Handle._value);
                return str;
            }
            else if (this.NamespaceDefinition != null && this.NamespaceDefinition.Name != null)
            {
                str = this.NamespaceDefinition.ToString(false) + "." + Name.Value;
                if (includeHandleValue)
                    str += string.Format(" ({0:x})", Handle._value);
                return str;
            }
            str = Name.Value + string.Format(" ({0:x})", Handle._value);
            if (includeHandleValue)
                str += string.Format(" ({0:x})", Handle._value);
            return str;
        }
    }

    public partial class TypeReference
    {
        public override string ToString()
        {
            return ToString(false);
        }
        public override string ToString(bool includeHandleValue)
        {
            string s = "";
            if (ParentNamespaceOrType is NamespaceReference)
                s += ParentNamespaceOrType.ToString(false) + ".";
            if (ParentNamespaceOrType is TypeReference)
                s += ParentNamespaceOrType.ToString(false) + "+";
            s += TypeName.Value;
            if (includeHandleValue)
                s += string.Format(" ({0:x})", Handle._value);
            return s;
        }
    }

    public partial class TypeForwarder
    {
        public override string ToString()
        {
            return this.Name.Value + " -> " + this.Scope.Name.Value;
        }
    }

    public partial class GenericParameter
    {
        public override string ToString()
        {
            return Kind.ToString() + " " + Name.Value + "(" + Number.ToString() + ")";
        }
    }

    public partial class Field
    {
        public override string ToString()
        {
            return Name.Value;
        }
    }

    public partial class Method
    {
        public override string ToString()
        {
            return Signature.ToString(Name.Value);
        }
    }

    public partial class QualifiedMethod
    {
        public override string ToString()
        {
            return EnclosingType.ToString(false) + "." + Method.ToString();
        }
    }

    public partial class Property
    {
        public override string ToString()
        {
            return Name.Value;
        }
    }

    public partial class Event
    {
        public override string ToString()
        {
            return Name.Value;
        }
    }

    public partial class SZArraySignature
    {
        public override string ToString()
        {
            return ElementType.ToString() + "[]";
        }
    }

    public partial class ArraySignature
    {
        public override string ToString()
        {
            return ElementType.ToString() + "[" + new string(',', Rank - 1) + "]";
        }
    }

    public partial class TypeSpecification
    {
        public override string ToString()
        {
            return Signature.ToString();
        }
    }

    public partial class TypeInstantiationSignature
    {
        public override string ToString()
        {
            return this.GenericType.ToString() + "<" + string.Join(", ", this.GenericTypeArguments) + ">";
        }
    }

    /* COMPLETENESS
    public partial class MethodImpl
    {
        public override string ToString()
        {
            return this.MethodDeclaration.ToString();
        }
    }*/

    public partial class MethodInstantiation
    {
        public override string ToString()
        {
            return Method.ToString()
                + "(Arguments: "
                + "<"
                + string.Join(", ", this.GenericTypeArguments)
                + ">";
        }
    }

    public partial class ByReferenceSignature
    {
        public override string ToString()
        {
            return "ref " + Type.ToString();
        }
    }

    public partial class CustomAttribute
    {
        public override string ToString()
        {
            string str = Constructor.ToString();
            str += "(" + string.Join(", ", FixedArguments)
                + string.Join(", ", NamedArguments) + ")";
            str += "(ctor: " + Constructor.Handle.ToString();
            return str;
        }
    }

    public partial class NamedArgument
    {
        public override string ToString()
        {
            return Name + " = " + Value.ToString();
        }
    }

    public partial class MemberReference
    {
        public override string ToString()
        {
            return Parent.ToString() + "." + Name.Value + " (Signature: " + Signature.ToString() + ")";
        }
    }

    public partial class MethodSemantics
    {
        public override string ToString()
        {
            string str = Enum.GetName(Attributes);
            return str + " : " + Method.ToString();
        }
    }

    public partial class MethodSignature
    {
        public override string ToString()
        {
            return ToString(" ");
        }

        public string ToString(string name)
        {
            return string.Join(" ", new string[] {
                CallingConvention.FlagsToString(),
                ReturnType.ToString(false),
                name
                    + (GenericParameterCount == 0 ? "" : "`" + GenericParameterCount.ToString())
                    + "(" + string.Join(", ", Parameters.Select(p => p.ToString(false))) +
                    string.Join(", ", VarArgParameters.Select(p => p.ToString(false))) + ")"}.Where(e => !string.IsNullOrWhiteSpace(e)));
        }
    }

    public partial class PropertySignature
    {
        public override string ToString()
        {
            return string.Join(" ", Enum.GetName(CallingConvention),
                Type.ToString()) + "(" + ToString(Parameters) + ")";
        }
    }

    public partial class FieldSignature
    {
        public override string ToString()
        {
            return Type.ToString();
        }
    }

    public partial class ModifiedType
    {
        public override string ToString()
        {
            return "[" + (IsOptional ? "opt : " : "req : ") + ModifierType.ToString() + "] " +
                Type.ToString();
        }
    }

    public partial class TypeVariableSignature
    {
        public override string ToString()
        {
            return "!" + Number;
        }
    }

    public partial class MethodTypeVariableSignature
    {
        public override string ToString()
        {
            return "!!" + Number;
        }
    }

    public partial class Parameter
    {
        public override string ToString()
        {
            string flags = Flags.ToString();
            return string.Format("{0}{1} (Seq:{2}) {3}",
                flags,
                Name.ToString(),
                Sequence,
                (DefaultValue == null ? "" : " = " + DefaultValue.ToString()));
        }
    }

    public partial class PointerSignature
    {
        public override string ToString()
        {
            return Type.ToString() + "*";
        }
    }

    public static class EnumHelpers
    {
        public static string FlagsToString(this SignatureCallingConvention value)
        {
            var values = new List<string>();
            if ((value & SignatureCallingConvention.HasThis) == SignatureCallingConvention.HasThis)
            {
                values.Add(nameof(SignatureCallingConvention.HasThis));
                value &= ~SignatureCallingConvention.HasThis;
            }
            if ((value & SignatureCallingConvention.ExplicitThis) == SignatureCallingConvention.ExplicitThis)
            {
                values.Add(nameof(SignatureCallingConvention.ExplicitThis));
                value &= ~SignatureCallingConvention.ExplicitThis;
            }
            if ((value & SignatureCallingConvention.UnmanagedCallingConventionMask) != default)
            {
                values.Add((value & SignatureCallingConvention.UnmanagedCallingConventionMask).ToString());
                value &= ~SignatureCallingConvention.UnmanagedCallingConventionMask;
            }
            if (value != default)
            {
                values.Add(value.ToString());
            }
            return values.Count == 0 ? "" : "[" + string.Join(" | ", values) + "] ";
        }
    }

    // SequenceEqual on IEnumerable is painfully slow and allocates memory.
    public static class SequenceExtensions
    {
        public static bool SequenceEqual<T>(this List<T> first, List<T> second)
        {
            return first.SequenceEqual(second, null);
        }

        public static bool SequenceEqual<T>(this List<T> first, List<T> second, IEqualityComparer<T> comparer)
        {
            return CollectionsMarshal.AsSpan(first).SequenceEqual(CollectionsMarshal.AsSpan(second), comparer);
        }
    }

    // Distinguishes positive and negative zeros for float and double values
    public static class CustomComparer
    {
        public static bool Equals(float x, float y)
        {
            return BitConverter.SingleToInt32Bits(x) == BitConverter.SingleToInt32Bits(y);
        }

        public static bool Equals(double x, double y)
        {
            return BitConverter.DoubleToInt64Bits(x) == BitConverter.DoubleToInt64Bits(y);
        }
    }

    public sealed class SingleComparer : IEqualityComparer<float>
    {
        public static readonly SingleComparer Instance = new SingleComparer();

        public bool Equals(float x, float y) => CustomComparer.Equals(x, y);
        public int GetHashCode(float obj) => obj.GetHashCode();
    }

    public sealed class DoubleComparer : IEqualityComparer<double>
    {
        public static readonly DoubleComparer Instance = new DoubleComparer();

        public bool Equals(double x, double y) => CustomComparer.Equals(x, y);
        public int GetHashCode(double obj) => obj.GetHashCode();
    }
}