Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ internal abstract partial class SerializationContextBase : IDisposable
private readonly Func<object?> readObjectDelegate;
private readonly Func<TypeRef?> readTypeRefDelegate;

private readonly ImmutableDictionary<string, object?>.Builder metadataBuilder = ImmutableDictionary.CreateBuilder<string, object?>();
private readonly Stack<ImmutableArray<TypeRef?>.Builder> typeRefBuilders = new Stack<ImmutableArray<TypeRef?>.Builder>();

private readonly byte[] guidBuffer = new byte[128 / 8];
Expand All @@ -59,6 +58,14 @@ internal abstract partial class SerializationContextBase : IDisposable
private static readonly object BoxedTrue = true;
private static readonly object BoxedFalse = false;

private static readonly IReadOnlyDictionary<string, object?> EmptyMetadata = ImmutableDictionary<string, object?>.Empty;
private static readonly object BoxedCreationPolicyAny = CreationPolicy.Any;
private static readonly object BoxedCreationPolicyShared = CreationPolicy.Shared;
private static readonly object BoxedCreationPolicyNonShared = CreationPolicy.NonShared;
private static readonly object BoxedInt32Zero = 0;
private static readonly object BoxedInt32One = 1;
private static readonly object BoxedInt32NegativeOne = -1;

internal SerializationContextBase(BinaryReader reader, Resolver resolver)
{
Requires.NotNull(reader, nameof(reader));
Expand Down Expand Up @@ -751,6 +758,41 @@ protected Array ReadArray(BinaryReader reader, Func<object?> itemReader, Type el
throw new NotSupportedException();
}

// Use typed fast paths for common element types to avoid
// the reflection overhead of Array.CreateInstance + Array.SetValue.
if (elementType == typeof(object))
{
var array = new object?[(int)count];
for (int i = 0; i < array.Length; i++)
{
array[i] = itemReader();
}

return array;
}

if (elementType == typeof(string))
{
var array = new string?[(int)count];
for (int i = 0; i < array.Length; i++)
{
array[i] = (string?)itemReader();
}

return array;
}

if (elementType == typeof(Type))
{
var array = new Type?[(int)count];
for (int i = 0; i < array.Length; i++)
{
array[i] = (Type?)itemReader();
}

return array;
}

var list = Array.CreateInstance(elementType, (int)count);
for (int i = 0; i < list.Length; i++)
{
Expand Down Expand Up @@ -801,7 +843,7 @@ protected void Write(IReadOnlyDictionary<string, object?> metadata)
// implicitly resolving TypeRefs to Types which is undesirable.
metadata = LazyMetadataWrapper.TryUnwrap(metadata);

serializedMetadata = new LazyMetadataWrapper(metadata.ToImmutableDictionary(), LazyMetadataWrapper.Direction.ToSubstitutedValue, this.Resolver);
serializedMetadata = new LazyMetadataWrapper(metadata.ToDictionary(), LazyMetadataWrapper.Direction.ToSubstitutedValue, this.Resolver);

foreach (var entry in serializedMetadata)
{
Expand All @@ -815,32 +857,22 @@ protected void Write(IReadOnlyDictionary<string, object?> metadata)
{
using (this.Trace("Metadata"))
{
// PERF TIP: if ReadMetadata shows up on startup perf traces,
// we could simply read the blob containing the metadata into a byte[]
// and defer actually deserializing it until such time as the metadata
// is actually required.
// We might do this with minimal impact to other code by implementing
// IReadOnlyDictionary<string, object> ourselves such that on the first
// access of any of its contents, we'll do a just-in-time deserialization,
// and perhaps only of the requested values.
uint count = this.ReadCompressedUInt();
var metadata = ImmutableDictionary<string, object?>.Empty;

if (count > 0)
if (count == 0)
{
var builder = this.metadataBuilder; // reuse builder to save on GC pressure
for (int i = 0; i < count; i++)
{
string? key = this.ReadString();
object? value = this.ReadObject();
builder.Add(key, value);
}
return EmptyMetadata;
}

metadata = builder.ToImmutable();
builder.Clear(); // clean up for the next user.
var dictionary = new Dictionary<string, object?>((int)count);
for (int i = 0; i < count; i++)
{
string? key = this.ReadString();
object? value = this.ReadObject();
dictionary.Add(key, value);
}

return new LazyMetadataWrapper(metadata, LazyMetadataWrapper.Direction.ToOriginalValue, this.Resolver);
return new LazyMetadataWrapper(dictionary, LazyMetadataWrapper.Direction.ToOriginalValue, this.Resolver);
}
}

Expand Down Expand Up @@ -1093,7 +1125,14 @@ protected void WriteObject(object? value)
case ObjectType.UInt64:
return this.reader.ReadUInt64();
case ObjectType.Int32:
return this.reader.ReadInt32();
int int32Value = this.reader.ReadInt32();
return int32Value switch
{
0 => BoxedInt32Zero,
1 => BoxedInt32One,
-1 => BoxedInt32NegativeOne,
_ => int32Value,
};
case ObjectType.UInt32:
return this.reader.ReadUInt32();
case ObjectType.Int16:
Expand All @@ -1115,7 +1154,13 @@ protected void WriteObject(object? value)
case ObjectType.Guid:
return this.ReadGuid();
case ObjectType.CreationPolicy:
return (CreationPolicy)this.reader.ReadByte();
return this.reader.ReadByte() switch
{
(byte)CreationPolicy.Any => BoxedCreationPolicyAny,
(byte)CreationPolicy.Shared => BoxedCreationPolicyShared,
(byte)CreationPolicy.NonShared => BoxedCreationPolicyNonShared,
byte b => (CreationPolicy)b,
};
case ObjectType.Type:
return this.ReadTypeRef().Resolve();
case ObjectType.TypeRef:
Expand Down
20 changes: 10 additions & 10 deletions src/Microsoft.VisualStudio.Composition/LazyMetadataWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ internal class LazyMetadataWrapper : ExportProvider.IMetadataDictionary
/// The underlying metadata, which may be partially translated since value translation may choose
/// to persist the translated result.
/// </summary>
protected ImmutableDictionary<string, object?> underlyingMetadata;
protected Dictionary<string, object?> underlyingMetadata;

internal LazyMetadataWrapper(ImmutableDictionary<string, object?> metadata, Direction direction, Resolver resolver)
internal LazyMetadataWrapper(Dictionary<string, object?> metadata, Direction direction, Resolver resolver)
{
Requires.NotNull(metadata, nameof(metadata));
Requires.NotNull(resolver, nameof(resolver));
Expand Down Expand Up @@ -253,7 +253,7 @@ internal static bool TryGetLoadSafeValueTypeRef(IReadOnlyDictionary<string, obje

protected virtual LazyMetadataWrapper Clone(LazyMetadataWrapper oldVersion, IReadOnlyDictionary<string, object?> newMetadata)
Comment thread
davkean marked this conversation as resolved.
{
return new LazyMetadataWrapper(newMetadata.ToImmutableDictionary(), oldVersion.direction, this.resolver);
return new LazyMetadataWrapper(newMetadata.ToDictionary(), oldVersion.direction, this.resolver);
}

protected object? SubstituteValueIfRequired(string key, object? value)
Expand All @@ -265,13 +265,13 @@ protected virtual LazyMetadataWrapper Clone(LazyMetadataWrapper oldVersion, IRea
return null;
}

value = this.SubstituteValueIfRequired(value);

// Update our metadata dictionary with the substitution to avoid
// the translation costs next time.
this.underlyingMetadata = this.underlyingMetadata.SetItem(key, value);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this no longer applicable?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who are you asking? I see a code comment below that seems to justify it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It added this comment after I asked this. :) I could not get it to resolve the question.


return value;
// Note: we intentionally do NOT write the substituted value back into the dictionary.
// The old ImmutableDictionary-backed implementation called SetItem here, which was
// structurally thread-safe (it returned a new dictionary and assigned the reference
// atomically). With Dictionary, concurrent writes would corrupt the internal state.
// The substitution cost without caching is negligible: TypeRef.Resolve() caches its
// result in a field, and Enum.ToObject is trivial.
return this.SubstituteValueIfRequired(value);
}

protected virtual object SubstituteValueIfRequired(object value)
Expand Down
18 changes: 9 additions & 9 deletions src/Microsoft.VisualStudio.Composition/LazyServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace Microsoft.VisualStudio.Composition
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand All @@ -19,6 +20,7 @@ internal static class LazyServices
{
private static readonly MethodInfo CreateStronglyTypedLazyOfTMValue = typeof(LazyServices).GetTypeInfo().GetMethod("CreateStronglyTypedLazyOfTM", BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo CreateStronglyTypedLazyOfTValue = typeof(LazyServices).GetTypeInfo().GetMethod("CreateStronglyTypedLazyOfT", BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly ConcurrentDictionary<(Type ExportType, Type? MetadataViewType), Func<AssemblyName, Func<object?>, object, object>> LazyFactoryCache = new();
private static readonly string Lazy1FullName = typeof(Lazy<>).FullName!;
private static readonly string Lazy2FullName = typeof(Lazy<,>).FullName!;

Expand Down Expand Up @@ -79,17 +81,15 @@ internal static Lazy<T> FromValue<T>(T value)
/// <returns>A function that takes a Func{object} value factory and metadata, and produces a Lazy{T, TMetadata} instance.</returns>
internal static Func<AssemblyName, Func<object?>, object, object> CreateStronglyTypedLazyFactory(Type? exportType, Type? metadataViewType)
{
MethodInfo genericMethod;
if (metadataViewType != null)
var key = (exportType ?? DefaultExportedValueType, metadataViewType);
return LazyFactoryCache.GetOrAdd(key, static k =>
{
genericMethod = CreateStronglyTypedLazyOfTMValue.MakeGenericMethod(exportType ?? DefaultExportedValueType, metadataViewType);
}
else
{
genericMethod = CreateStronglyTypedLazyOfTValue.MakeGenericMethod(exportType ?? DefaultExportedValueType);
}
MethodInfo genericMethod = k.MetadataViewType != null
? CreateStronglyTypedLazyOfTMValue.MakeGenericMethod(k.ExportType, k.MetadataViewType)
: CreateStronglyTypedLazyOfTValue.MakeGenericMethod(k.ExportType);

return (Func<AssemblyName, Func<object?>, object, object>)genericMethod.CreateDelegate(typeof(Func<AssemblyName, Func<object?>, object, object>));
return (Func<AssemblyName, Func<object?>, object, object>)genericMethod.CreateDelegate(typeof(Func<AssemblyName, Func<object?>, object, object>));
});
}

internal static Func<T> AsFunc<T>(this Lazy<T> lazy)
Expand Down
51 changes: 9 additions & 42 deletions src/Microsoft.VisualStudio.Composition/RuntimeComposition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -321,13 +321,8 @@ public bool Equals(RuntimePart? other)
[DebuggerDisplay("{" + nameof(ImportingSiteElementType) + "}")]
public class RuntimeImport : IEquatable<RuntimeImport>
{
private NullableBool isLazy;
private Type? importingSiteElementType;
private Func<AssemblyName, Func<object?>, object, object>? lazyFactory;
private ParameterInfo? importingParameter;
private MemberInfo? importingMember;
private volatile bool isMetadataTypeInitialized;
private Type? metadataType;

private RuntimeImport(TypeRef importingSiteTypeRef, TypeRef importingSiteTypeWithoutCollectionRef, ImportCardinality cardinality, IReadOnlyList<RuntimeExport> satisfyingExports, bool isNonSharedInstanceRequired, bool isExportFactory, IReadOnlyDictionary<string, object?> metadata, IReadOnlyCollection<string> exportFactorySharingBoundaries)
{
Expand Down Expand Up @@ -399,18 +394,7 @@ public Type? ExportFactory

public ParameterInfo? ImportingParameter => this.importingParameter ?? (this.importingParameter = this.ImportingParameterRef?.ParameterInfo);

public bool IsLazy
{
get
{
if (!this.isLazy.HasValue)
{
this.isLazy = this.ImportingSiteTypeWithoutCollectionRef.IsAnyLazyType();
}

return this.isLazy.Value;
}
}
public bool IsLazy => this.ImportingSiteTypeWithoutCollectionRef.IsAnyLazyType();

public Type ImportingSiteType => this.ImportingSiteTypeRef.ResolvedType;

Expand All @@ -421,32 +405,15 @@ public bool IsLazy
/// <summary>
/// Gets the type of the member, with the ImportMany collection and Lazy/ExportFactory stripped off, when present.
/// </summary>
public Type ImportingSiteElementType
{
get
{
if (this.importingSiteElementType == null)
{
this.importingSiteElementType = PartDiscovery.GetTypeIdentityFromImportingType(this.ImportingSiteType, this.Cardinality == ImportCardinality.ZeroOrMore);
}

return this.importingSiteElementType;
}
}
public Type ImportingSiteElementType => PartDiscovery.GetTypeIdentityFromImportingType(this.ImportingSiteType, this.Cardinality == ImportCardinality.ZeroOrMore);

public Type? MetadataType
{
get
{
if (!this.isMetadataTypeInitialized)
{
this.metadataType = this.IsLazy && this.ImportingSiteTypeWithoutCollection.GenericTypeArguments.Length == 2
? this.ImportingSiteTypeWithoutCollection.GenericTypeArguments[1]
: null;
this.isMetadataTypeInitialized = true;
}

return this.metadataType;
return this.IsLazy && this.ImportingSiteTypeWithoutCollection.GenericTypeArguments.Length == 2
? this.ImportingSiteTypeWithoutCollection.GenericTypeArguments[1]
: null;
}
}

Expand All @@ -462,13 +429,13 @@ public TypeRef DeclaringTypeRef
{
get
{
if (this.lazyFactory == null && this.IsLazy)
if (!this.IsLazy)
{
Type[] lazyTypeArgs = this.ImportingSiteTypeWithoutCollection.GenericTypeArguments;
this.lazyFactory = LazyServices.CreateStronglyTypedLazyFactory(this.ImportingSiteElementType, lazyTypeArgs.Length > 1 ? lazyTypeArgs[1] : null);
return null;
}

return this.lazyFactory;
Type[] lazyTypeArgs = this.ImportingSiteTypeWithoutCollection.GenericTypeArguments;
return LazyServices.CreateStronglyTypedLazyFactory(this.ImportingSiteElementType, lazyTypeArgs.Length > 1 ? lazyTypeArgs[1] : null);
}
}

Expand Down
16 changes: 16 additions & 0 deletions src/Microsoft.VisualStudio.Composition/Utilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,5 +228,21 @@ internal static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IEnumer

return dictionary;
}

/// <summary>
/// Creates a <see cref="Dictionary{TKey, TValue}"/> from an <see cref="IReadOnlyDictionary{TKey, TValue}"/>.
/// The BCL Dictionary constructor does not accept IReadOnlyDictionary on netstandard2.0.
/// </summary>
internal static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> source)
where TKey : notnull
{
var dictionary = new Dictionary<TKey, TValue>(source.Count);
foreach (var kvp in source)
{
dictionary.Add(kvp.Key, kvp.Value);
}

return dictionary;
}
}
}
Loading