diff --git a/src/Compilers/Core/CodeAnalysisTest/ObjectSerializationTests.cs b/src/Compilers/Core/CodeAnalysisTest/ObjectSerializationTests.cs index 316099cf50f709bd7fbf4a32d0a1dee3141a1c05..9ae0f78a5eaaf1743eb7676edc99aecaa01beb0c 100644 --- a/src/Compilers/Core/CodeAnalysisTest/ObjectSerializationTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/ObjectSerializationTests.cs @@ -28,7 +28,7 @@ private void TestInvalidStreamVersion() private void RoundTrip(Action writeAction, Action readAction, bool recursive) { var stream = new MemoryStream(); - var binder = new RecordingObjectBinder(); + var binder = new ObjectBinder(); var writer = new StreamObjectWriter(stream, binder: binder, recursive: recursive); writeAction(writer); @@ -53,7 +53,7 @@ private void TestRoundTrip(Action writeAction, Action(T value, Action writeAction, Func readAction, bool recursive) { var stream = new MemoryStream(); - var binder = new RecordingObjectBinder(); + var binder = new ObjectBinder(); var writer = new StreamObjectWriter(stream, binder: binder, recursive: recursive); writeAction(writer, value); @@ -83,7 +83,21 @@ private void TestRoundTrip(T value, Action writeAction, Func private T RoundTripValue(T value, bool recursive) { - return RoundTrip(value, (w, v) => w.WriteValue(v), r => (T)r.ReadValue(), recursive); + return RoundTrip(value, + (w, v) => + { + if (v != null && v.GetType().IsEnum) + { + w.WriteInt64(Convert.ToInt64((object)v)); + } + else + { + w.WriteValue(v); + } + }, + r => value != null && value.GetType().IsEnum + ? (T)Enum.ToObject(typeof(T), r.ReadInt64()) + : (T)r.ReadValue(), recursive); } private void TestRoundTripValue(T value, bool recursive) @@ -142,12 +156,21 @@ public TypeWithOneMember(T value) private TypeWithOneMember(ObjectReader reader) { - _member = (T)reader.ReadValue(); + _member = typeof(T).IsEnum + ? (T)Enum.ToObject(typeof(T), reader.ReadInt64()) + : (T)reader.ReadValue(); } void IObjectWritable.WriteTo(ObjectWriter writer) { - writer.WriteValue(_member); + if (typeof(T).IsEnum) + { + writer.WriteInt64(Convert.ToInt64(_member)); + } + else + { + writer.WriteValue(_member); + } } Func IObjectReadable.GetReader() => (r) => new TypeWithOneMember(r); @@ -644,6 +667,60 @@ public void TestUInt32Values() TestRoundTripValue(Int32.MaxValue); } + [Fact] + public void TestInt64Values() + { + TestRoundTripValue(0); + TestRoundTripValue(1); + TestRoundTripValue(2); + TestRoundTripValue(3); + TestRoundTripValue(4); + TestRoundTripValue(5); + TestRoundTripValue(6); + TestRoundTripValue(7); + TestRoundTripValue(8); + TestRoundTripValue(9); + TestRoundTripValue(10); + TestRoundTripValue(-1); + TestRoundTripValue(Byte.MinValue); + TestRoundTripValue(Byte.MaxValue); + TestRoundTripValue(Int16.MinValue); + TestRoundTripValue(Int16.MaxValue); + TestRoundTripValue(UInt16.MinValue); + TestRoundTripValue(UInt16.MaxValue); + TestRoundTripValue(Int32.MinValue); + TestRoundTripValue(Int32.MaxValue); + TestRoundTripValue(UInt32.MinValue); + TestRoundTripValue(UInt32.MaxValue); + TestRoundTripValue(Int64.MinValue); + TestRoundTripValue(Int64.MaxValue); + } + + [Fact] + public void TestUInt64Values() + { + TestRoundTripValue(0); + TestRoundTripValue(1); + TestRoundTripValue(2); + TestRoundTripValue(3); + TestRoundTripValue(4); + TestRoundTripValue(5); + TestRoundTripValue(6); + TestRoundTripValue(7); + TestRoundTripValue(8); + TestRoundTripValue(9); + TestRoundTripValue(10); + TestRoundTripValue(Byte.MinValue); + TestRoundTripValue(Byte.MaxValue); + TestRoundTripValue(UInt16.MinValue); + TestRoundTripValue(UInt16.MaxValue); + TestRoundTripValue(Int32.MaxValue); + TestRoundTripValue(UInt32.MinValue); + TestRoundTripValue(UInt32.MaxValue); + TestRoundTripValue(UInt64.MinValue); + TestRoundTripValue(UInt64.MaxValue); + } + [Fact] public void TestPrimitiveMemberValues() { @@ -814,15 +891,18 @@ private static void TestWritingPrimitiveValues(ObjectWriter writer) writer.WriteValue("\uDC00\uD800"); // invalid surrogate pair writer.WriteValue("\uD800"); // incomplete surrogate pair writer.WriteValue(null); - writer.WriteValue(ConsoleColor.Cyan); - writer.WriteValue(EByte.Value); - writer.WriteValue(ESByte.Value); - writer.WriteValue(EShort.Value); - writer.WriteValue(EUShort.Value); - writer.WriteValue(EInt.Value); - writer.WriteValue(EUInt.Value); - writer.WriteValue(ELong.Value); - writer.WriteValue(EULong.Value); + unchecked + { + writer.WriteInt64((long)ConsoleColor.Cyan); + writer.WriteInt64((long)EByte.Value); + writer.WriteInt64((long)ESByte.Value); + writer.WriteInt64((long)EShort.Value); + writer.WriteInt64((long)EUShort.Value); + writer.WriteInt64((long)EInt.Value); + writer.WriteInt64((long)EUInt.Value); + writer.WriteInt64((long)ELong.Value); + writer.WriteInt64((long)EULong.Value); + } writer.WriteValue(typeof(object)); writer.WriteValue(_testNow); } @@ -850,15 +930,20 @@ private static void TestReadingPrimitiveValues(ObjectReader reader) Assert.Equal("\uDC00\uD800", (String)reader.ReadValue()); // invalid surrogate pair Assert.Equal("\uD800", (String)reader.ReadValue()); // incomplete surrogate pair Assert.Equal(null, reader.ReadValue()); - Assert.Equal(ConsoleColor.Cyan, reader.ReadValue()); - Assert.Equal(EByte.Value, reader.ReadValue()); - Assert.Equal(ESByte.Value, reader.ReadValue()); - Assert.Equal(EShort.Value, reader.ReadValue()); - Assert.Equal(EUShort.Value, reader.ReadValue()); - Assert.Equal(EInt.Value, reader.ReadValue()); - Assert.Equal(EUInt.Value, reader.ReadValue()); - Assert.Equal(ELong.Value, reader.ReadValue()); - Assert.Equal(EULong.Value, reader.ReadValue()); + + unchecked + { + Assert.Equal((long)ConsoleColor.Cyan, reader.ReadInt64()); + Assert.Equal((long)EByte.Value, reader.ReadInt64()); + Assert.Equal((long)ESByte.Value, reader.ReadInt64()); + Assert.Equal((long)EShort.Value, reader.ReadInt64()); + Assert.Equal((long)EUShort.Value, reader.ReadInt64()); + Assert.Equal((long)EInt.Value, reader.ReadInt64()); + Assert.Equal((long)EUInt.Value, reader.ReadInt64()); + Assert.Equal((long)ELong.Value, reader.ReadInt64()); + Assert.Equal((long)EULong.Value, reader.ReadInt64()); + } + Assert.Equal(typeof(object), (Type)reader.ReadValue()); Assert.Equal(_testNow, (DateTime)reader.ReadValue()); } @@ -977,7 +1062,7 @@ public void TestObjectMapLimits() instances.Add(new TypeWithTwoMembers(i, i.ToString())); } - var binder = new RecordingObjectBinder(); + var binder = new ObjectBinder(); var writer = new StreamObjectWriter(stream, binder: binder); // Write each instance twice. The second time around, they'll become ObjectRefs for (int pass = 0; pass < 2; pass++) diff --git a/src/Compilers/Core/Portable/CodeAnalysis.csproj b/src/Compilers/Core/Portable/CodeAnalysis.csproj index dc39d65517d7e5a5530a7b298a3df047f00f4858..7abb2b5dc268259119268ffd654574e30ad6485f 100644 --- a/src/Compilers/Core/Portable/CodeAnalysis.csproj +++ b/src/Compilers/Core/Portable/CodeAnalysis.csproj @@ -32,14 +32,12 @@ - - diff --git a/src/Compilers/Core/Portable/Serialization/FixedObjectBinder.cs b/src/Compilers/Core/Portable/Serialization/FixedObjectBinder.cs deleted file mode 100644 index 3faf3b529db066c95434b62708a43b84dec50962..0000000000000000000000000000000000000000 --- a/src/Compilers/Core/Portable/Serialization/FixedObjectBinder.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; - -namespace Roslyn.Utilities -{ - /// - /// An with a fixed set of type and reader mappings. - /// - internal class FixedObjectBinder : ObjectBinder - { - private readonly ImmutableDictionary _typeMap; - private readonly ImmutableDictionary> _readerMap; - - public FixedObjectBinder( - ImmutableDictionary typeMap, - ImmutableDictionary> readerMap) - { - _typeMap = typeMap ?? ImmutableDictionary.Empty; - _readerMap = readerMap ?? ImmutableDictionary>.Empty; - } - - public static readonly FixedObjectBinder Empty = new FixedObjectBinder(null, null); - - public override bool TryGetType(TypeKey key, out Type type) - { - return _typeMap.TryGetValue(key, out type); - } - - public override bool TryGetTypeKey(Type type, out TypeKey key) - { - // do not let types have keys that cannot be reverse mapped. - return base.TryGetTypeKey(type, out key) && _typeMap.ContainsKey(key); - } - - public override bool TryGetWriter(Object instance, out Action writer) - { - // don't let objects be written that do not have known readers. - return base.TryGetWriter(instance, out writer) && _readerMap.ContainsKey(instance.GetType()); - } - - public override bool TryGetReader(Type type, out Func reader) - { - return _readerMap.TryGetValue(type, out reader); - } - } -} diff --git a/src/Compilers/Core/Portable/Serialization/ObjectBinder.cs b/src/Compilers/Core/Portable/Serialization/ObjectBinder.cs index 617fa0fa0f257104a6099992ae2e7b226eec315f..88d8953e71c0d00be7b7f9f7c2b11a873621cfc1 100644 --- a/src/Compilers/Core/Portable/Serialization/ObjectBinder.cs +++ b/src/Compilers/Core/Portable/Serialization/ObjectBinder.cs @@ -1,46 +1,93 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Concurrent; +using System.Diagnostics; using System.Reflection; namespace Roslyn.Utilities { /// - /// A type that provides object and type encoding/decoding. + /// A that records runtime types and object readers during object writing so they + /// can be used to read back objects later. /// - internal abstract class ObjectBinder + /// + /// This binder records runtime types an object readers as a way to avoid needing to describe all serialization types up front + /// or using reflection to determine them on demand. + /// + internal sealed class ObjectBinder { + private readonly ConcurrentDictionary _typeMap = + new ConcurrentDictionary(); + + private readonly ConcurrentDictionary> _readerMap = + new ConcurrentDictionary>(); + /// /// Gets the corresponding to the specified . /// Returns false if no type corresponds to the key. /// - public abstract bool TryGetType(TypeKey key, out Type type); + public bool TryGetType(TypeKey key, out Type type) + => _typeMap.TryGetValue(key, out type); + + public bool TryGetReader(Type type, out Func reader) + => _readerMap.TryGetValue(type, out reader); + + private void RecordType(Type type, TypeKey key) + { + if (type != null) + { + _typeMap.TryAdd(key, type); + } + } + + private void RecordReader(object instance) + { + if (instance != null) + { + var type = instance.GetType(); + + var key = GetAndRecordTypeKey(type); + + var readable = instance as IObjectReadable; + if (readable != null) + { + if (_readerMap.ContainsKey(type)) + { + Debug.Assert(_typeMap.ContainsKey(key)); + } + else + { + _readerMap.TryAdd(type, readable.GetReader()); + } + } + } + } /// /// Gets the for the specified . - /// Returns false if the type cannot be serialized. /// - public virtual bool TryGetTypeKey(Type type, out TypeKey key) + public TypeKey GetAndRecordTypeKey(Type type) { - key = new TypeKey(type.GetTypeInfo().Assembly.FullName, type.FullName); - return true; + var key = new TypeKey(type.GetTypeInfo().Assembly.FullName, type.FullName); ; + RecordType(type, key); + return key; } - /// - /// Gets a function that reads an type's members from an and constructs an instance with those members. - /// Returns false if the type cannot be deserialized. - /// - public abstract bool TryGetReader(Type type, out Func reader); + private static readonly Action s_writer + = (w, i) => ((IObjectWritable)i).WriteTo(w); /// /// Gets a function that writes an object's members to a . /// Returns false if the type cannot be serialized. /// - public virtual bool TryGetWriter(object instance, out Action writer) - { + public bool TryGetWriter(Object instance, out Action writer) + { + RecordReader(instance); + if (instance is IObjectWritable) { - writer = (w, i) => ((IObjectWritable)i).WriteTo(w); // static delegate should be cached + writer = s_writer; return true; } else @@ -50,4 +97,4 @@ public virtual bool TryGetWriter(object instance, out Action internal sealed class RecordingObjectBinder : ObjectBinder { - private readonly ConcurrentDictionary _typeMap = - new ConcurrentDictionary(); + private readonly Dictionary _typeMap_mustLock = new Dictionary(); private readonly ConcurrentDictionary> _readerMap = - new ConcurrentDictionary>(); + new ConcurrentDictionary>(concurrencyLevel: 2, capacity: 64); public override bool TryGetType(TypeKey key, out Type type) { - return _typeMap.TryGetValue(key, out type); + lock (_typeMap_mustLock) + { + return _typeMap_mustLock.TryGetValue(key, out type); + } } public override bool TryGetTypeKey(Type type, out TypeKey key) @@ -56,7 +59,10 @@ private void RecordType(Type type, TypeKey key) { if (type != null) { - _typeMap.TryAdd(key, type); + lock (_typeMap_mustLock) + { + _typeMap_mustLock[key] = type; + } } } @@ -74,7 +80,12 @@ private void RecordReader(object instance) { if (_readerMap.ContainsKey(type)) { - Debug.Assert(_typeMap.ContainsKey(key)); +#if DEBUG + lock (_typeMap_mustLock) + { + Debug.Assert(_typeMap_mustLock.ContainsKey(key)); + } +#endif } else { diff --git a/src/Compilers/Core/Portable/Serialization/StreamObjectReader.cs b/src/Compilers/Core/Portable/Serialization/StreamObjectReader.cs index 72c5268b74b373537c76657fdcf1a0dc83a86864..4d519822a3be1ce7b06fec6a0322847b14e5dbd6 100644 --- a/src/Compilers/Core/Portable/Serialization/StreamObjectReader.cs +++ b/src/Compilers/Core/Portable/Serialization/StreamObjectReader.cs @@ -36,14 +36,15 @@ internal sealed partial class StreamObjectReader : ObjectReader, IDisposable internal const byte VersionByte2 = 0b00000001; private readonly BinaryReader _reader; - private readonly ObjectBinder _binder; + private readonly ObjectBinder _binderOpt; private readonly bool _recursive; private readonly CancellationToken _cancellationToken; /// /// Map of reference id's to deserialized objects. /// - private readonly ReferenceMap _referenceMap; + private readonly ReaderReferenceMap _objectReferenceMap; + private readonly ReaderReferenceMap _stringReferenceMap; /// /// Stack of values used to construct objects and arrays @@ -88,8 +89,9 @@ internal sealed partial class StreamObjectReader : ObjectReader, IDisposable _recursive = IsRecursive(stream); _reader = new BinaryReader(stream, Encoding.UTF8); - _referenceMap = new ReferenceMap(knownObjects); - _binder = binder ?? FixedObjectBinder.Empty; + _objectReferenceMap = new ReaderReferenceMap(knownObjects); + _stringReferenceMap = new ReaderReferenceMap(knownObjects); + _binderOpt = binder; _cancellationToken = cancellationToken; if (!_recursive) @@ -142,7 +144,8 @@ internal static bool IsRecursive(Stream stream) public void Dispose() { - _referenceMap.Dispose(); + _objectReferenceMap.Dispose(); + _stringReferenceMap.Dispose(); if (!_recursive) { @@ -417,11 +420,14 @@ private Variant ReadVariant() case EncodingKind.StringRef_1Byte: case EncodingKind.StringRef_2Bytes: return Variant.FromString(ReadStringValue(kind)); - case EncodingKind.Object: case EncodingKind.ObjectRef_4Bytes: + return Variant.FromObject(_objectReferenceMap.GetValue(_reader.ReadInt32())); case EncodingKind.ObjectRef_1Byte: + return Variant.FromObject(_objectReferenceMap.GetValue(_reader.ReadByte())); case EncodingKind.ObjectRef_2Bytes: - return ReadObject(kind); + return Variant.FromObject(_objectReferenceMap.GetValue(_reader.ReadUInt16())); + case EncodingKind.Object: + return ReadObject(); case EncodingKind.Type: case EncodingKind.TypeRef_4Bytes: case EncodingKind.TypeRef_1Byte: @@ -552,16 +558,16 @@ public override Object ReadValue() /// /// An reference-id to object map, that can share base data efficiently. /// - private class ReferenceMap + private class ReaderReferenceMap where T : class { private readonly ObjectData _baseData; private readonly int _baseDataCount; - private readonly List _values; + private readonly List _values; - internal static readonly ObjectPool> s_objectListPool - = new ObjectPool>(() => new List(20)); + internal static readonly ObjectPool> s_objectListPool + = new ObjectPool>(() => new List(20)); - public ReferenceMap(ObjectData baseData) + public ReaderReferenceMap(ObjectData baseData) { _baseData = baseData; _baseDataCount = baseData != null ? _baseData.Objects.Length : 0; @@ -580,18 +586,18 @@ public int GetNextReferenceId() return _baseDataCount + _values.Count - 1; } - public void SetValue(int referenceId, object value) + public void SetValue(int referenceId, T value) { _values[referenceId - _baseDataCount] = value; } - public object GetValue(int referenceId) + public T GetValue(int referenceId) { if (_baseData != null) { if (referenceId < _baseDataCount) { - return _baseData.Objects[referenceId]; + return (T)_baseData.Objects[referenceId]; } else { @@ -645,13 +651,13 @@ private string ReadStringValue(EncodingKind kind) switch (kind) { case EncodingKind.StringRef_1Byte: - return (string)_referenceMap.GetValue(_reader.ReadByte()); + return _stringReferenceMap.GetValue(_reader.ReadByte()); case EncodingKind.StringRef_2Bytes: - return (string)_referenceMap.GetValue(_reader.ReadUInt16()); + return _stringReferenceMap.GetValue(_reader.ReadUInt16()); case EncodingKind.StringRef_4Bytes: - return (string)_referenceMap.GetValue(_reader.ReadInt32()); + return _stringReferenceMap.GetValue(_reader.ReadInt32()); case EncodingKind.StringUtf16: case EncodingKind.StringUtf8: @@ -664,7 +670,7 @@ private string ReadStringValue(EncodingKind kind) private unsafe string ReadStringLiteral(EncodingKind kind) { - int id = _referenceMap.GetNextReferenceId(); + int id = _stringReferenceMap.GetNextReferenceId(); string value; if (kind == EncodingKind.StringUtf8) { @@ -681,7 +687,7 @@ private unsafe string ReadStringLiteral(EncodingKind kind) } } - _referenceMap.SetValue(id, value); + _stringReferenceMap.SetValue(id, value); return value; } @@ -710,8 +716,8 @@ private Variant ReadArray(EncodingKind kind) // SUBTLE: If it was a primitive array, only the EncodingKind byte of the element type was written, instead of encoding as a type. var elementKind = (EncodingKind)_reader.ReadByte(); - Type elementType; - if (StreamObjectWriter.s_reverseTypeMap.TryGetValue(elementKind, out elementType)) + var elementType = StreamObjectWriter.s_reverseTypeMap[(int)elementKind]; + if (elementType != null) { return Variant.FromArray(this.ReadPrimitiveTypeArrayElements(elementType, elementKind, length)); } @@ -758,7 +764,7 @@ private Variant ConstructArray(Type elementType, int length) private Array ReadPrimitiveTypeArrayElements(Type type, EncodingKind kind, int length) { - Debug.Assert(StreamObjectWriter.s_reverseTypeMap[kind] == type); + Debug.Assert(StreamObjectWriter.s_reverseTypeMap[(int)kind] == type); // optimizations for supported array type by binary reader if (type == typeof(byte)) @@ -968,26 +974,26 @@ private Type ReadType(EncodingKind kind) switch (kind) { case EncodingKind.TypeRef_1Byte: - return (Type)_referenceMap.GetValue(_reader.ReadByte()); + return (Type)_objectReferenceMap.GetValue(_reader.ReadByte()); case EncodingKind.TypeRef_2Bytes: - return (Type)_referenceMap.GetValue(_reader.ReadUInt16()); + return (Type)_objectReferenceMap.GetValue(_reader.ReadUInt16()); case EncodingKind.TypeRef_4Bytes: - return (Type)_referenceMap.GetValue(_reader.ReadInt32()); + return (Type)_objectReferenceMap.GetValue(_reader.ReadInt32()); case EncodingKind.Type: - int id = _referenceMap.GetNextReferenceId(); + int id = _objectReferenceMap.GetNextReferenceId(); var assemblyName = this.ReadStringValue(); var typeName = this.ReadStringValue(); Type type; - if (!_binder.TryGetType(new TypeKey(assemblyName, typeName), out type)) + if (_binderOpt == null || !_binderOpt.TryGetType(new TypeKey(assemblyName, typeName), out type)) { throw NoSerializationTypeException(typeName); } - _referenceMap.SetValue(id, type); + _objectReferenceMap.SetValue(id, type); return type; default: @@ -1043,53 +1049,39 @@ private object ReadBoxedEnum() throw ExceptionUtilities.UnexpectedValue(enumType); } - private Variant ReadObject(EncodingKind kind) + private Variant ReadObject() { - switch (kind) - { - case EncodingKind.ObjectRef_4Bytes: - return Variant.FromObject(_referenceMap.GetValue(_reader.ReadInt32())); - case EncodingKind.ObjectRef_1Byte: - return Variant.FromObject(_referenceMap.GetValue(_reader.ReadByte())); - case EncodingKind.ObjectRef_2Bytes: - return Variant.FromObject(_referenceMap.GetValue(_reader.ReadUInt16())); + int id = _objectReferenceMap.GetNextReferenceId(); - case EncodingKind.Object: - int id = _referenceMap.GetNextReferenceId(); + Type type = this.ReadType(); - Type type = this.ReadType(); - - Func typeReader; - if (!_binder.TryGetReader(type, out typeReader)) - { - throw NoSerializationReaderException(type.FullName); - } + Func typeReader; + if (_binderOpt == null || !_binderOpt.TryGetReader(type, out typeReader)) + { + throw NoSerializationReaderException(type.FullName); + } - if (_recursive) - { - // recursive: read and construct instance immediately from member elements encoding next in the stream - var instance = typeReader(this); - _referenceMap.SetValue(id, instance); - return Variant.FromObject(instance); - } - else - { - uint memberCount = this.ReadCompressedUInt(); - - if (memberCount == 0) - { - return ConstructObject(type, (int)memberCount, typeReader, id); - } - else - { - // non-recursive: remember construction information to invoke later when member elements available on the stack - _constructionStack.Push(Construction.CreateObjectConstruction(type, (int)memberCount, _valueStack.Count, typeReader, id)); - return Variant.None; - } - } + if (_recursive) + { + // recursive: read and construct instance immediately from member elements encoding next in the stream + var instance = typeReader(this); + _objectReferenceMap.SetValue(id, instance); + return Variant.FromObject(instance); + } + else + { + uint memberCount = this.ReadCompressedUInt(); - default: - throw ExceptionUtilities.UnexpectedValue(kind); + if (memberCount == 0) + { + return ConstructObject(type, (int)memberCount, typeReader, id); + } + else + { + // non-recursive: remember construction information to invoke later when member elements available on the stack + _constructionStack.Push(Construction.CreateObjectConstruction(type, (int)memberCount, _valueStack.Count, typeReader, id)); + return Variant.None; + } } } @@ -1114,7 +1106,7 @@ private Variant ConstructObject(Type type, int memberCount, Func - /// Map of serialized object's reference ids. + /// Map of serialized object's reference ids. The object-reference-map uses refernece equality + /// for performance. While the string-reference-map uses value-equality for greater cache hits + /// and reuse. /// - private readonly ReferenceMap _referenceMap; + private readonly WriterReferenceMap _objectReferenceMap; + private readonly WriterReferenceMap _stringReferenceMap; /// /// The stack of values (object members or array elements) in order to be emitted. @@ -77,8 +80,9 @@ internal sealed partial class StreamObjectWriter : ObjectWriter, IDisposable Debug.Assert(BitConverter.IsLittleEndian); _writer = new BinaryWriter(stream, Encoding.UTF8); - _referenceMap = new ReferenceMap(knownObjects); - _binder = binder ?? FixedObjectBinder.Empty; + _objectReferenceMap = new WriterReferenceMap(knownObjects, valueEquality: false); + _stringReferenceMap = new WriterReferenceMap(knownObjects, valueEquality: true); + _binderOpt = binder; _recursive = recursive; _cancellationToken = cancellationToken; @@ -105,7 +109,8 @@ private void WriteVersion() public void Dispose() { - _referenceMap.Dispose(); + _objectReferenceMap.Dispose(); + _stringReferenceMap.Dispose(); if (!_recursive) { @@ -190,6 +195,7 @@ public override void WriteString(string value) public override void WriteValue(object value) { + Debug.Assert(value == null || !value.GetType().GetTypeInfo().IsEnum, "Enum should not be written with WriteValue. Write them as ints instead."); if (_recursive) { WriteVariant(Variant.FromBoxedObject(value)); @@ -450,65 +456,79 @@ public override void WriteValue(object value) /// /// An object reference to reference-id map, that can share base data efficiently. /// - private class ReferenceMap + private class WriterReferenceMap { - private readonly ImmutableDictionary _baseMap; + private readonly Dictionary _baseMap; private readonly Dictionary _valueToIdMap; + private readonly bool _valueEquality; private int _nextId; - // note: uses value equality so strings get unified for better compaction - private static readonly ObjectPool> s_dictionaryPool = + private static readonly ObjectPool> s_referenceDictionaryPool = + new ObjectPool>(() => new Dictionary(128, ReferenceEqualityComparer.Instance)); + + private static readonly ObjectPool> s_valueDictionaryPool = new ObjectPool>(() => new Dictionary(128)); - private ReferenceMap(ImmutableDictionary baseMap) + private WriterReferenceMap(Dictionary baseMap, bool valueEquality) { _baseMap = baseMap; - _valueToIdMap = s_dictionaryPool.Allocate(); + _valueEquality = valueEquality; + _valueToIdMap = GetDictionaryPool().Allocate(); _nextId = _baseMap != null ? _baseMap.Count : 0; } - public ReferenceMap(ObjectData data) - : this(data != null ? GetBaseMap(data) : null) + private ObjectPool> GetDictionaryPool() + => _valueEquality ? s_valueDictionaryPool : s_referenceDictionaryPool; + + public WriterReferenceMap(ObjectData data, bool valueEquality) + : this(data != null ? GetBaseMap(data, valueEquality) : null, valueEquality) { } - private static readonly ConditionalWeakTable> s_baseDataMap - = new ConditionalWeakTable>(); + private static readonly ConditionalWeakTable> s_referenceBaseDataMap + = new ConditionalWeakTable>(); - private static ImmutableDictionary GetBaseMap(ObjectData data) + private static readonly ConditionalWeakTable> s_valueBaseDataMap + = new ConditionalWeakTable>(); + + private static Dictionary GetBaseMap(ObjectData data, bool valueEquality) { - ImmutableDictionary baseData; - if (!s_baseDataMap.TryGetValue(data, out baseData)) + var dataMap = valueEquality ? s_valueBaseDataMap : s_referenceBaseDataMap; + + Dictionary baseData; + if (!dataMap.TryGetValue(data, out baseData)) { - baseData = s_baseDataMap.GetValue(data, CreateBaseMap); + baseData = dataMap.GetValue(data, CreateBaseMap); } return baseData; } - private static ImmutableDictionary CreateBaseMap(ObjectData data) + private static Dictionary CreateBaseMap(ObjectData data) { - var builder = ImmutableDictionary.Empty.ToBuilder(); + var builder = new Dictionary(); for (int i = 0; i < data.Objects.Length; i++) { builder.Add(data.Objects[i], i); } - return builder.ToImmutable(); + return builder; } public void Dispose() { + var pool = GetDictionaryPool(); + // If the map grew too big, don't return it to the pool. // When testing with the Roslyn solution, this dropped only 2.5% of requests. if (_valueToIdMap.Count > 1024) { - s_dictionaryPool.ForgetTrackedObject(_valueToIdMap); + pool.ForgetTrackedObject(_valueToIdMap); } else { _valueToIdMap.Clear(); - s_dictionaryPool.Free(_valueToIdMap); + pool.Free(_valueToIdMap); } } @@ -573,7 +593,7 @@ private unsafe void WriteStringValue(string value) else { int id; - if (_referenceMap.TryGetReferenceId(value, out id)) + if (_stringReferenceMap.TryGetReferenceId(value, out id)) { Debug.Assert(id >= 0); if (id <= byte.MaxValue) @@ -594,7 +614,7 @@ private unsafe void WriteStringValue(string value) } else { - _referenceMap.Add(value); + _stringReferenceMap.Add(value); if (value.IsValidUnicodeString()) { @@ -910,7 +930,7 @@ private void WritePrimitiveType(Type type, EncodingKind kind) private void WriteType(Type type) { int id; - if (_referenceMap.TryGetReferenceId(type, out id)) + if (_objectReferenceMap.TryGetReferenceId(type, out id)) { Debug.Assert(id >= 0); if (id <= byte.MaxValue) @@ -931,16 +951,17 @@ private void WriteType(Type type) } else { - _referenceMap.Add(type); + _objectReferenceMap.Add(type); _writer.Write((byte)EncodingKind.Type); - TypeKey key; - if (!_binder.TryGetTypeKey(type, out key)) + if (_binderOpt == null) { throw NoSerializationTypeException(type.FullName); } + var key = _binderOpt.GetAndRecordTypeKey(type); + this.WriteStringValue(key.AssemblyName); this.WriteStringValue(key.TypeName); } @@ -962,7 +983,7 @@ private void WriteObject(object instance) // write object ref if we already know this instance int id; - if (_referenceMap.TryGetReferenceId(instance, out id)) + if (_objectReferenceMap.TryGetReferenceId(instance, out id)) { Debug.Assert(id >= 0); if (id <= byte.MaxValue) @@ -984,7 +1005,7 @@ private void WriteObject(object instance) else { Action typeWriter; - if (!_binder.TryGetWriter(instance, out typeWriter)) + if (_binderOpt == null || !_binderOpt.TryGetWriter(instance, out typeWriter)) { throw NoSerializationWriterException(instance.GetType().FullName); } @@ -1026,7 +1047,7 @@ private void WriteObject(object instance) private void WriteObjectHeader(object instance, uint memberCount) { - _referenceMap.Add(instance); + _objectReferenceMap.Add(instance); _writer.Write((byte)EncodingKind.Object); @@ -1050,27 +1071,44 @@ private static Exception NoSerializationWriterException(string typeName) } // we have s_typeMap and s_reversedTypeMap since there is no bidirectional map in compiler - internal static readonly ImmutableDictionary s_typeMap = ImmutableDictionary.CreateRange( - new KeyValuePair[] - { - KeyValuePair.Create(typeof(bool), EncodingKind.BooleanType), - KeyValuePair.Create(typeof(char), EncodingKind.Char), - KeyValuePair.Create(typeof(string), EncodingKind.StringType), - KeyValuePair.Create(typeof(sbyte), EncodingKind.Int8), - KeyValuePair.Create(typeof(short), EncodingKind.Int16), - KeyValuePair.Create(typeof(int), EncodingKind.Int32), - KeyValuePair.Create(typeof(long), EncodingKind.Int64), - KeyValuePair.Create(typeof(byte), EncodingKind.UInt8), - KeyValuePair.Create(typeof(ushort), EncodingKind.UInt16), - KeyValuePair.Create(typeof(uint), EncodingKind.UInt32), - KeyValuePair.Create(typeof(ulong), EncodingKind.UInt64), - KeyValuePair.Create(typeof(float), EncodingKind.Float4), - KeyValuePair.Create(typeof(double), EncodingKind.Float8), - KeyValuePair.Create(typeof(decimal), EncodingKind.Decimal), - }); - - internal static readonly ImmutableDictionary s_reverseTypeMap - = s_typeMap.ToImmutableDictionary(kv => kv.Value, kv => kv.Key); + // Note: s_typeMap is effectively immutable. However, for maxiumum perf we use mutable types because + // they are used in hotspots. + internal static readonly Dictionary s_typeMap; + + /// + /// Indexed by EncodingKind. + /// + internal static readonly ImmutableArray s_reverseTypeMap; + + static StreamObjectWriter() + { + s_typeMap = new Dictionary + { + { typeof(bool), EncodingKind.BooleanType }, + { typeof(char), EncodingKind.Char }, + { typeof(string), EncodingKind.StringType }, + { typeof(sbyte), EncodingKind.Int8 }, + { typeof(short), EncodingKind.Int16 }, + { typeof(int), EncodingKind.Int32 }, + { typeof(long), EncodingKind.Int64 }, + { typeof(byte), EncodingKind.UInt8 }, + { typeof(ushort), EncodingKind.UInt16 }, + { typeof(uint), EncodingKind.UInt32 }, + { typeof(ulong), EncodingKind.UInt64 }, + { typeof(float), EncodingKind.Float4 }, + { typeof(double), EncodingKind.Float8 }, + { typeof(decimal), EncodingKind.Decimal }, + }; + + var temp = new Type[(int)EncodingKind.Last]; + + foreach (var kvp in s_typeMap) + { + temp[(int)kvp.Value] = kvp.Key; + } + + s_reverseTypeMap = ImmutableArray.Create(temp); + } /// /// byte marker mask for encoding compressed uint @@ -1420,7 +1458,10 @@ internal enum EncodingKind : byte /// /// The string type /// - StringType + StringType, + + + Last = StringType + 1, } internal enum VariantKind @@ -1445,16 +1486,16 @@ internal enum VariantKind BoxedEnum, DateTime, Array, - Type + Type, } internal struct Variant { public readonly VariantKind Kind; - private readonly decimal _image; + private readonly long _image; private readonly object _instance; - private Variant(VariantKind kind, decimal image, object instance = null) + private Variant(VariantKind kind, long image, object instance = null) { Kind = kind; _image = image; @@ -1506,7 +1547,7 @@ public static Variant FromUInt32(uint value) public static Variant FromUInt64(ulong value) { - return new Variant(VariantKind.UInt64, value); + return new Variant(VariantKind.UInt64, unchecked((long)value)); } public static Variant FromSingle(float value) @@ -1531,7 +1572,7 @@ public static Variant FromString(string value) public static Variant FromDecimal(Decimal value) { - return new Variant(VariantKind.Decimal, value); + return new Variant(VariantKind.Decimal, image: 0, instance: value); } public static Variant FromBoxedEnum(object value) @@ -1604,31 +1645,31 @@ public uint AsUInt32() public long AsInt64() { Debug.Assert(Kind == VariantKind.Int64); - return (long)_image; + return _image; } public ulong AsUInt64() { Debug.Assert(Kind == VariantKind.UInt64); - return (ulong)_image; + return unchecked((ulong)_image); } public decimal AsDecimal() { Debug.Assert(Kind == VariantKind.Decimal); - return _image; + return (decimal)_instance; } public float AsSingle() { Debug.Assert(Kind == VariantKind.Float4); - return (float)BitConverter.Int64BitsToDouble((long)_image); + return (float)BitConverter.Int64BitsToDouble(_image); } public double AsDouble() { Debug.Assert(Kind == VariantKind.Float8); - return BitConverter.Int64BitsToDouble((long)_image); + return BitConverter.Int64BitsToDouble(_image); } public char AsChar() @@ -1658,7 +1699,7 @@ public object AsBoxedEnum() public DateTime AsDateTime() { Debug.Assert(Kind == VariantKind.DateTime); - return DateTime.FromBinary((long)_image); + return DateTime.FromBinary(_image); } public Type AsType() @@ -1673,6 +1714,8 @@ public Array AsArray() return (Array)_instance; } + private static readonly PropertyInfo s_getTypeCode = typeof(Type).GetRuntimeProperty("TypeCode"); + public static Variant FromBoxedObject(object value) { if (value == null) @@ -1683,72 +1726,36 @@ public static Variant FromBoxedObject(object value) { var type = value.GetType(); var typeInfo = type.GetTypeInfo(); + Debug.Assert(!typeInfo.IsEnum, "Enums should not be written with WriteObject. Write them out as integers instead."); - if (typeInfo.IsEnum) - { - return FromBoxedEnum(value); - } - else if (type == typeof(bool)) - { - return FromBoolean((bool)value); - } - else if (type == typeof(int)) - { - return FromInt32((int)value); - } - else if (type == typeof(string)) - { - return FromString((string)value); - } - else if (type == typeof(short)) - { - return FromInt16((short)value); - } - else if (type == typeof(long)) - { - return FromInt64((long)value); - } - else if (type == typeof(char)) - { - return FromChar((char)value); - } - else if (type == typeof(sbyte)) - { - return FromSByte((sbyte)value); - } - else if (type == typeof(byte)) - { - return FromByte((byte)value); - } - else if (type == typeof(ushort)) - { - return FromUInt16((ushort)value); - } - else if (type == typeof(uint)) - { - return FromUInt32((uint)value); - } - else if (type == typeof(ulong)) - { - return FromUInt64((ulong)value); - } - else if (type == typeof(decimal)) - { - return FromDecimal((decimal)value); - } - else if (type == typeof(float)) - { - return FromSingle((float)value); - } - else if (type == typeof(double)) - { - return FromDouble((double)value); - } - else if (type == typeof(DateTime)) + // Perf: Note that JIT optimizes each expression value.GetType() == typeof(T) to a single register comparison. + // Also the checks are sorted by commonality of the checked types. + + // The primitive types are Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single. + if (typeInfo.IsPrimitive) { - return FromDateTime((DateTime)value); + // Note: int, double, bool, char, have been chosen to go first as they're they + // common values of literals in code, and so would be hte likely hits if we do + // have a primitive type we're serializing out. + if (value.GetType() == typeof(int)) { return FromInt32((int)value); } + if (value.GetType() == typeof(double)) { return FromDouble((double)value); } + if (value.GetType() == typeof(bool)) { return FromBoolean((bool)value); } + if (value.GetType() == typeof(char)) { return FromChar((char)value); } + if (value.GetType() == typeof(byte)) { return FromByte((byte)value); } + if (value.GetType() == typeof(short)) { return FromInt16((short)value); } + if (value.GetType() == typeof(long)) { return FromInt64((long)value); } + if (value.GetType() == typeof(sbyte)) { return FromSByte((sbyte)value); } + if (value.GetType() == typeof(float)) { return FromSingle((float)value); } + if (value.GetType() == typeof(ushort)) { return FromUInt16((ushort)value); } + if (value.GetType() == typeof(uint)) { return FromUInt32((uint)value); } + if (value.GetType() == typeof(ulong)) { return FromUInt64((ulong)value); } } - else if (type.IsArray) + + if (value.GetType() == typeof(decimal)) { return FromDecimal((decimal)value); } + if (value.GetType() == typeof(DateTime)) { return FromDateTime((DateTime)value); } + if (value.GetType() == typeof(string)) { return FromString((string)value); } + + if (type.IsArray) { var instance = (Array)value; @@ -1759,14 +1766,10 @@ public static Variant FromBoxedObject(object value) return Variant.FromArray(instance); } - else if (value is Type) - { - return Variant.FromType((Type)value); - } - else - { - return Variant.FromObject(value); - } + + return value is Type t + ? Variant.FromType(t) + : Variant.FromObject(value); } } @@ -1820,4 +1823,4 @@ public object ToBoxedObject() } } } -} +} \ No newline at end of file diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index d556a24d7bc809f8939a97e1bf075849f9b2c550..8dd30a13de02d19059410378bc93fd053ec1e2a9 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -1234,7 +1234,7 @@ public bool IsEquivalentTo(SyntaxNode node, bool topLevel = false) return IsEquivalentToCore(node, topLevel); } - internal static readonly ObjectBinder s_defaultBinder = new RecordingObjectBinder(); + internal static readonly ObjectBinder s_defaultBinder = new ObjectBinder(); public virtual void SerializeTo(Stream stream, CancellationToken cancellationToken = default(CancellationToken)) { diff --git a/src/Workspaces/Core/Portable/Workspaces.csproj b/src/Workspaces/Core/Portable/Workspaces.csproj index 22158d6372a534c9412ba060ed81732f48807f24..1d09acbd7fea185f5ed1e16eb917019f66931c61 100644 --- a/src/Workspaces/Core/Portable/Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Workspaces.csproj @@ -260,9 +260,6 @@ InternalUtilities\UnicodeCharacterUtilities.cs - - Serialization\FixedObjectBinder.cs - Serialization\IObjectReadable.cs @@ -281,9 +278,6 @@ Serialization\ObjectWriter.cs - - Serialization\RecordingObjectBinder.cs - Serialization\StreamObjectReader.cs