//
#define CORINFO_MAXINDIRECTIONS 4
#define CORINFO_USEHELPER ((uint16_t) 0xffff)
+#define CORINFO_USENULL ((uint16_t) 0xfffe)
#define CORINFO_NO_SIZE_CHECK ((uint16_t) 0xffff)
struct CORINFO_RUNTIME_LOOKUP
// Number of indirections to get there
// CORINFO_USEHELPER = don't know how to get it, so use helper function at run-time instead
+ // CORINFO_USENULL = the context should be null because the callee doesn't actually use it
// 0 = use the this pointer itself (e.g. token is C<!0> inside code in sealed class C)
// or method desc itself (e.g. token is method void M::mymeth<!!0>() inside code in M::mymeth)
// Otherwise, follow each byte-offset stored in the "offsets[]" array (may be negative)
1. pLookup->indirections == CORINFO_USEHELPER : Call a helper passing it the
instantiation-specific handle, and the tokens to lookup the handle.
- 2. pLookup->indirections != CORINFO_USEHELPER :
+ 2. pLookup->indirections == CORINFO_USENULL : Pass null. Callee won't dereference
+ the context.
+ 3. pLookup->indirections != CORINFO_USEHELPER or CORINFO_USENULL :
2a. pLookup->testForNull == false : Dereference the instantiation-specific handle
to get the handle.
2b. pLookup->testForNull == true : Dereference the instantiation-specific handle.
return gtNewRuntimeLookupHelperCallNode(pRuntimeLookup, ctxTree, compileTimeHandle);
}
+#ifdef FEATURE_READYTORUN
+ if (pRuntimeLookup->indirections == CORINFO_USENULL)
+ {
+ return gtNewIconNode(0, TYP_I_IMPL);
+ }
+#endif
+
// Slot pointer
GenTree* slotPtrTree = ctxTree;
// If pRuntimeLookup->indirections is equal to CORINFO_USEHELPER, it specifies that a run-time helper should be
// used; otherwise, it specifies the number of indirections via pRuntimeLookup->offsets array.
- if ((pRuntimeLookup->indirections == CORINFO_USEHELPER) || pRuntimeLookup->testForNull ||
- pRuntimeLookup->testForFixup)
+ if ((pRuntimeLookup->indirections == CORINFO_USEHELPER) || (pRuntimeLookup->indirections == CORINFO_USENULL) ||
+ pRuntimeLookup->testForNull || pRuntimeLookup->testForFixup)
{
// If the first condition is true, runtime lookup tree is available only via the run-time helper function.
// TODO-CQ If the second or third condition is true, we are always using the slow path since we can't
// This accounts for up to 2 indirections to get at a dictionary followed by a possible spill slot
public const uint MAXINDIRECTIONS = 4;
public const ushort USEHELPER = 0xffff;
+ public const ushort USENULL = 0xfffe;
public const ushort CORINFO_NO_SIZE_CHECK = 0xffff;
}
int pointerSize = _nodeFactory.Target.PointerSize;
GenericLookupResult lookup = ReadyToRunGenericHelperNode.GetLookupSignature(_nodeFactory, lookupKind, targetOfLookup);
- int dictionarySlot = dictionaryLayout.GetSlotForFixedEntry(lookup);
- if (dictionarySlot != -1)
+ if (dictionaryLayout.TryGetSlotForEntry(lookup, out int dictionarySlot))
{
int dictionaryOffset = dictionarySlot * pointerSize;
return GenericDictionaryLookup.CreateFixedLookup(contextSource, vtableOffset, dictionaryOffset, indirectLastOffset: indirectLastOffset);
}
}
+ else
+ {
+ return GenericDictionaryLookup.CreateNullLookup(contextSource);
+ }
}
}
/// <summary>
/// Get a slot index for a given entry. Slot indices are never expected to change once given out.
/// </summary>
- public abstract int GetSlotForEntry(GenericLookupResult entry);
+ public abstract bool TryGetSlotForEntry(GenericLookupResult entry, out int slot);
- /// <summary>
- /// Get the slot for an entry which is fixed already. Otherwise return -1
- /// </summary>
- /// <param name="entry"></param>
- /// <returns></returns>
- public virtual int GetSlotForFixedEntry(GenericLookupResult entry)
+ public abstract IEnumerable<GenericLookupResult> Entries
{
- return GetSlotForEntry(entry);
+ get;
}
- public abstract IEnumerable<GenericLookupResult> Entries
+ public abstract bool IsEmpty
{
get;
}
public class PrecomputedDictionaryLayoutNode : DictionaryLayoutNode
{
private readonly GenericLookupResult[] _layout;
+ private readonly GenericLookupResult[] _discardedSlots;
public override bool HasFixedSlots => true;
- public PrecomputedDictionaryLayoutNode(TypeSystemEntity owningMethodOrType, IEnumerable<GenericLookupResult> layout)
+ public override bool IsEmpty => _layout.Length == 0;
+
+ public PrecomputedDictionaryLayoutNode(TypeSystemEntity owningMethodOrType, GenericLookupResult[] layout, GenericLookupResult[] discardedSlots)
: base(owningMethodOrType)
{
- ArrayBuilder<GenericLookupResult> l = default(ArrayBuilder<GenericLookupResult>);
- foreach (var entry in layout)
- l.Add(entry);
-
- _layout = l.ToArray();
+ _layout = layout;
+ _discardedSlots = discardedSlots;
}
public override void EnsureEntry(GenericLookupResult entry)
{
- int index = Array.IndexOf(_layout, entry);
-
- if (index == -1)
- {
- // Using EnsureEntry to add a slot to a PrecomputedDictionaryLayoutNode is not supported
- throw new NotSupportedException();
- }
+ throw new NotSupportedException();
}
- public override int GetSlotForEntry(GenericLookupResult entry)
+ public override bool TryGetSlotForEntry(GenericLookupResult entry, out int slot)
{
- int index = Array.IndexOf(_layout, entry);
+ slot = Array.IndexOf(_layout, entry);
+
+ // If this is a slot we should discard, respond false
+ if (slot < 0 && Array.IndexOf(_discardedSlots, entry) >= 0)
+ return false;
// This entry wasn't precomputed. If this is an optimized build with scanner, it might suggest
// the scanner didn't see the need for this. There is a discrepancy between scanning and compiling.
// This is a fatal error to prevent bad codegen.
- if (index < 0)
+ if (slot < 0)
throw new InvalidOperationException($"{OwningMethodOrType}: {entry}");
- return index;
+ return true;
}
public override IEnumerable<GenericLookupResult> Entries
_layout = layout;
}
- public override int GetSlotForEntry(GenericLookupResult entry)
+ public override bool TryGetSlotForEntry(GenericLookupResult entry, out int slot)
{
if (_layout == null)
ComputeLayout();
- int index = Array.IndexOf(_layout, entry);
+ slot = Array.IndexOf(_layout, entry);
// We never called EnsureEntry on this during compilation?
// This is a fatal error to prevent bad codegen.
- if (index < 0)
+ if (slot < 0)
throw new InvalidOperationException($"{OwningMethodOrType}: {entry}");
- return index;
+ return true;
}
public override IEnumerable<GenericLookupResult> Entries
return _layout;
}
}
+
+ public override bool IsEmpty
+ {
+ get
+ {
+ if (_layout == null)
+ ComputeLayout();
+
+ return _layout.Length == 0;
+ }
+ }
}
}
if (!relocsOnly)
{
// The concrete slot won't be known until we're emitting data - don't ask for it in relocsOnly.
- dictionarySlot = factory.GenericDictionaryLayout(_dictionaryOwner).GetSlotForEntry(lookup);
+ if (!factory.GenericDictionaryLayout(_dictionaryOwner).TryGetSlotForEntry(lookup, out dictionarySlot))
+ {
+ encoder.EmitMOV(result, 0);
+ return;
+ }
}
// Load the generic dictionary cell
if (!relocsOnly)
{
// The concrete slot won't be known until we're emitting data - don't ask for it in relocsOnly.
- dictionarySlot = factory.GenericDictionaryLayout(_dictionaryOwner).GetSlotForEntry(lookup);
+ if (!factory.GenericDictionaryLayout(_dictionaryOwner).TryGetSlotForEntry(lookup, out dictionarySlot))
+ {
+ encoder.EmitMOV(result, (ushort)0);
+ return;
+ }
}
// Load the generic dictionary cell
if (!relocsOnly)
{
// The concrete slot won't be known until we're emitting data - don't ask for it in relocsOnly.
- dictionarySlot = factory.GenericDictionaryLayout(_dictionaryOwner).GetSlotForEntry(lookup);
+ if (!factory.GenericDictionaryLayout(_dictionaryOwner).TryGetSlotForEntry(lookup, out dictionarySlot))
+ {
+ encoder.EmitMOV(result, (ushort)0);
+ return;
+ }
}
// Load the generic dictionary cell
if (!relocsOnly)
{
// The concrete slot won't be known until we're emitting data - don't ask for it in relocsOnly.
- dictionarySlot = factory.GenericDictionaryLayout(_dictionaryOwner).GetSlotForEntry(lookup);
+ if (!factory.GenericDictionaryLayout(_dictionaryOwner).TryGetSlotForEntry(lookup, out dictionarySlot))
+ {
+ encoder.EmitZeroReg(result);
+ return;
+ }
}
// Load the generic dictionary cell
public struct GenericDictionaryLookup
{
private const short UseHelperOffset = -1;
+ private const short UseNullOffset = -2;
private readonly object _helperObject;
}
}
+ public bool UseNull
+ {
+ get
+ {
+ return _offset1 == UseNullOffset;
+ }
+ }
+
/// <summary>
/// Gets the number of indirections to follow. Only valid if <see cref="UseHelper"/> is false.
/// </summary>
{
get
{
- Debug.Assert(!UseHelper);
+ Debug.Assert(!UseHelper && !UseNull);
return ContextSource == GenericContextSource.MethodParameter ? 1 : 2;
}
}
{
get
{
- Debug.Assert(!UseHelper);
+ Debug.Assert(!UseHelper && !UseNull);
Debug.Assert(index < NumberOfIndirections);
switch (index)
{
{
get
{
- Debug.Assert(!UseHelper);
+ Debug.Assert(!UseHelper && !UseNull);
return _indirectLastOffset;
}
}
{
return new GenericDictionaryLookup(contextSource, UseHelperOffset, checked((short)helperId), helperObject, indirectLastOffset: false);
}
+
+ public static GenericDictionaryLookup CreateNullLookup(GenericContextSource contextSource)
+ {
+ return new GenericDictionaryLookup(contextSource, UseNullOffset, 0, null, false);
+ }
}
/// <summary>
private sealed class ScannedDictionaryLayoutProvider : DictionaryLayoutProvider
{
- private Dictionary<TypeSystemEntity, IEnumerable<GenericLookupResult>> _layouts = new Dictionary<TypeSystemEntity, IEnumerable<GenericLookupResult>>();
+ private Dictionary<TypeSystemEntity, (GenericLookupResult[] Slots, GenericLookupResult[] DiscardedSlots)> _layouts = new();
private HashSet<TypeSystemEntity> _entitiesWithForcedLazyLookups = new HashSet<TypeSystemEntity>();
public ScannedDictionaryLayoutProvider(NodeFactory factory, ImmutableArray<DependencyNodeCore<NodeFactory>> markedNodes)
if (node is DictionaryLayoutNode layoutNode)
{
TypeSystemEntity owningMethodOrType = layoutNode.OwningMethodOrType;
- _layouts.Add(owningMethodOrType, layoutNode.Entries);
+ GenericLookupResult[] layout = OptimizeSlots(factory, layoutNode.Entries, out GenericLookupResult[] discarded);
+ _layouts.Add(owningMethodOrType, (layout, discarded));
}
else if (node is ReadyToRunGenericHelperNode genericLookup
&& genericLookup.HandlesInvalidEntries(factory))
}
}
+ private static GenericLookupResult[] OptimizeSlots(NodeFactory factory, IEnumerable<GenericLookupResult> slots, out GenericLookupResult[] discarded)
+ {
+ ArrayBuilder<GenericLookupResult> slotBuilder = default;
+ ArrayBuilder<GenericLookupResult> discardedBuilder = default;
+
+ // We go over all slots in the layout, looking for references to method dictionaries
+ // that are going to be empty.
+ // Set those slots aside so that we can avoid generating the references to such dictionaries.
+ // We do this for methods only because method dictionaries have a high overhead (they
+ // get prefixed with a pointer-padded 32-bit hashcode and might end up in various
+ // summary tables as well).
+
+ foreach (GenericLookupResult lookupResult in slots)
+ {
+ if (lookupResult is MethodDictionaryGenericLookupResult methodDictLookup)
+ {
+ MethodDesc targetMethod = methodDictLookup.Method.GetCanonMethodTarget(CanonicalFormKind.Specific);
+ DictionaryLayoutNode targetLayout = factory.GenericDictionaryLayout(targetMethod);
+ if (targetLayout.IsEmpty)
+ {
+ discardedBuilder.Add(lookupResult);
+ continue;
+ }
+ }
+
+ slotBuilder.Add(lookupResult);
+ }
+
+ discarded = discardedBuilder.ToArray();
+ return slotBuilder.ToArray();
+ }
+
private PrecomputedDictionaryLayoutNode GetPrecomputedLayout(TypeSystemEntity methodOrType)
{
- if (!_layouts.TryGetValue(methodOrType, out IEnumerable<GenericLookupResult> layout))
+ if (!_layouts.TryGetValue(methodOrType, out var layout))
{
// If we couldn't find the dictionary layout information for this, it's because the scanner
// didn't correctly predict what will be needed.
Debug.Assert(false);
throw new ScannerFailedException($"A dictionary layout was not computed by the IL scanner.");
}
- return new PrecomputedDictionaryLayoutNode(methodOrType, layout);
+ return new PrecomputedDictionaryLayoutNode(methodOrType, layout.Slots, layout.DiscardedSlots);
}
public override DictionaryLayoutNode GetLayout(TypeSystemEntity methodOrType)
lookup.lookupKind.runtimeLookupFlags = (ushort)genericLookup.HelperId;
lookup.lookupKind.runtimeLookupArgs = (void*)ObjectToHandle(genericLookup.HelperObject);
}
+ else if (genericLookup.UseNull)
+ {
+ lookup.runtimeLookup.indirections = CORINFO.USENULL;
+ }
else
{
if (genericLookup.ContextSource == GenericContextSource.MethodParameter)