Skip to content

Commit

Permalink
Make TypeComponentsCache trimmable
Browse files Browse the repository at this point in the history
Contributes to dotnet#80165.

The compiler is not able to get rid of the call to `PerNameQueryCache<M>.Factory` virtual method due to generic code sharing. Making this not shareable allows trimming it because now we can see `ConcurrentUnifier<StringKey, __Canon>.Factory` is never called (whereas `ConcurrentUnifier<__Canon, __Canon>.Factory` is called).

This is a ~5 kB regression for apps that do use reflection.

We could explore different strategies to make this trimmable and not be a size regression, but I'm not sure if it's really worth it. We'd like to get rid of this reflection stack implementation anyway and replace it with something more lightweight.
  • Loading branch information
MichalStrehovsky committed Jan 17, 2023
1 parent 7237cb5 commit af8932d
Showing 1 changed file with 19 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Threading;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Reflection;
Expand Down Expand Up @@ -103,22 +104,38 @@ private static object[] CreatePerNameQueryCaches(RuntimeTypeInfo type, bool igno
// Generic cache for scenario specific data. For example, it is used to cache Enum names and values.
internal object? _genericCache;

// The sole purpose of this struct is to defeat generic code sharing of PerNameQueryCache below.
// We eagerly construct PerNameQueryCache when the type components cache is created, but they may not be
// actually used in an app that is light on reflection use. But we do have other canonically equivalent
// ConcurrentUnifier instances in the program and their use could drag this implementation into the app.
// Making the ConcurrentUnifier instance unshareable gives the compiler the ability to unshare this.
private struct StringKey : IEquatable<StringKey>
{
public readonly string Key;
public StringKey(string k) => Key = k;
public bool Equals(StringKey other) => Key == other.Key;
public override bool Equals([NotNullWhen(true)] object? obj) => obj is StringKey k && Equals(k);
public override int GetHashCode() => Key.GetHashCode();
public static implicit operator StringKey(string k) => new StringKey(k);
public static implicit operator string(StringKey k) => k.Key;
}

//
// Each PerName cache persists the results of a Type.Get(name, bindingFlags) for a particular MemberInfoType "M".
//
// where "bindingFlags" == Public | NonPublic | Instance | Static | FlattenHierarchy
//
// In addition, if "ignoreCase" was passed to the constructor, BindingFlags.IgnoreCase is also in effect.
//
private sealed class PerNameQueryCache<M> : ConcurrentUnifier<string, QueriedMemberList<M>> where M : MemberInfo
private sealed class PerNameQueryCache<M> : ConcurrentUnifier<StringKey, QueriedMemberList<M>> where M : MemberInfo
{
public PerNameQueryCache(RuntimeTypeInfo type, bool ignoreCase)
{
_type = type;
_ignoreCase = ignoreCase;
}

protected sealed override QueriedMemberList<M> Factory(string key)
protected sealed override QueriedMemberList<M> Factory(StringKey key)
{
QueriedMemberList<M> result = QueriedMemberList<M>.Create(_type, key, ignoreCase: _ignoreCase);
result.Compact();
Expand Down

0 comments on commit af8932d

Please sign in to comment.