| File: Compiler\DependencyAnalysis\DictionaryLayoutNode.cs | Web Access |
| Project: src\runtime\src\coreclr\tools\aot\ILCompiler.Compiler\ILCompiler.Compiler.csproj (ILCompiler.Compiler) |
// 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.Collections.Generic; using System.Diagnostics; using Internal.TypeSystem; using ILCompiler.DependencyAnalysisFramework; namespace ILCompiler.DependencyAnalysis { /// <summary> /// Represents the layout of the generic dictionary associated with a given canonical /// generic type or generic method. Maintains a bag of <see cref="GenericLookupResult"/> associated /// with the canonical entity. /// </summary> /// <remarks> /// The generic dictionary doesn't have any dependent nodes because <see cref="GenericLookupResult"/> /// are runtime-determined - the concrete dependency depends on the generic context the canonical /// entity is instantiated with. /// </remarks> public abstract class DictionaryLayoutNode : DependencyNodeCore<NodeFactory> { private readonly TypeSystemEntity _owningMethodOrType; public DictionaryLayoutNode(TypeSystemEntity owningMethodOrType) { _owningMethodOrType = owningMethodOrType; Validate(); } [Conditional("DEBUG")] private void Validate() { TypeDesc type = _owningMethodOrType as TypeDesc; if (type != null) { Debug.Assert(type.IsCanonicalSubtype(CanonicalFormKind.Any)); Debug.Assert(type.IsDefType); } else { MethodDesc method = _owningMethodOrType as MethodDesc; Debug.Assert(method != null && method.IsSharedByGenericInstantiations); } } public virtual ObjectNodeSection DictionarySection(NodeFactory factory) { if (factory.Target.IsWindows) { if (_owningMethodOrType is TypeDesc) { return ObjectNodeSection.FoldableReadOnlyDataSection; } else { // Method dictionary serves as an identity at runtime which means they are not foldable. Debug.Assert(_owningMethodOrType is MethodDesc); return ObjectNodeSection.ReadOnlyDataSection; } } else { return ObjectNodeSection.DataSection; } } /// <summary> /// Ensure that a generic lookup result can be resolved. Used to add new lookups to a dictionary which HasUnfixedSlots /// Must not be used after any calls to GetSlotForEntry /// </summary> public abstract void EnsureEntry(GenericLookupResult entry); /// <summary> /// Get a slot index for a given entry. Slot indices are never expected to change once given out. /// </summary> public abstract bool TryGetSlotForEntry(GenericLookupResult entry, out int slot); public abstract IEnumerable<GenericLookupResult> Entries { get; } public abstract bool IsEmpty { get; } public virtual IEnumerable<GenericLookupResult> FixedEntries => Entries; public TypeSystemEntity OwningMethodOrType => _owningMethodOrType; /// <summary> /// Gets a value indicating whether the slot assignment is determined at the node creation time. /// </summary> public abstract bool HasFixedSlots { get; } /// <summary> /// Gets a value indicating if this dictionary may have non fixed slots /// </summary> public virtual bool HasUnfixedSlots => !HasFixedSlots; public virtual ICollection<NativeLayoutVertexNode> GetTemplateEntries(NodeFactory factory) { ArrayBuilder<NativeLayoutVertexNode> templateEntries = default(ArrayBuilder<NativeLayoutVertexNode>); foreach (var entry in Entries) { templateEntries.Add(entry.TemplateDictionaryNode(factory)); } return templateEntries.ToArray(); } public virtual void EmitDictionaryData(ref ObjectDataBuilder builder, NodeFactory factory, GenericDictionaryNode dictionary, bool fixedLayoutOnly) { var context = new GenericLookupResultContext(dictionary.OwningEntity, dictionary.TypeInstantiation, dictionary.MethodInstantiation); IEnumerable<GenericLookupResult> entriesToEmit = fixedLayoutOnly ? FixedEntries : Entries; foreach (GenericLookupResult lookupResult in entriesToEmit) { #if DEBUG int offsetBefore = builder.CountBytes; #endif lookupResult.EmitDictionaryEntry(ref builder, factory, context, dictionary); #if DEBUG Debug.Assert(builder.CountBytes - offsetBefore == factory.Target.PointerSize); #endif } } public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory) { if (HasFixedSlots) { foreach (GenericLookupResult lookupResult in FixedEntries) { foreach (DependencyNodeCore<NodeFactory> dependency in lookupResult.NonRelocDependenciesFromUsage(factory)) { yield return new DependencyListEntry(dependency, "GenericLookupResultDependency"); } } } } public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory) { Debug.Assert(HasFixedSlots); NativeLayoutSavedVertexNode templateLayout; if (_owningMethodOrType is MethodDesc) { templateLayout = factory.NativeLayout.TemplateMethodLayout((MethodDesc)_owningMethodOrType); } else { templateLayout = factory.NativeLayout.TemplateTypeLayout((TypeDesc)_owningMethodOrType); } List<CombinedDependencyListEntry> conditionalDependencies = new List<CombinedDependencyListEntry>(); foreach (var lookupSignature in FixedEntries) { conditionalDependencies.Add(new CombinedDependencyListEntry(lookupSignature.TemplateDictionaryNode(factory), templateLayout, "Type loader template")); } return conditionalDependencies; } protected override string GetName(NodeFactory factory) => $"Dictionary layout for {_owningMethodOrType}"; public override bool HasConditionalStaticDependencies => HasFixedSlots; public override bool HasDynamicDependencies => false; public override bool InterestingForDynamicDependencyAnalysis => false; public override bool StaticDependenciesAreComputed => true; public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory factory) => null; } public class PrecomputedDictionaryLayoutNode : DictionaryLayoutNode { private readonly GenericLookupResult[] _layout; private readonly GenericLookupResult[] _discardedSlots; public override bool HasFixedSlots => true; public override bool IsEmpty => _layout.Length == 0; public PrecomputedDictionaryLayoutNode(TypeSystemEntity owningMethodOrType, GenericLookupResult[] layout, GenericLookupResult[] discardedSlots) : base(owningMethodOrType) { _layout = layout; _discardedSlots = discardedSlots; } public override void EnsureEntry(GenericLookupResult entry) { throw new NotSupportedException(); } public override bool TryGetSlotForEntry(GenericLookupResult entry, out int slot) { slot = Array.IndexOf(_layout, entry); // If we're looking for a low type handle load level and it doesn't exist, check for a higher load level. var necessaryLookup = entry as NecessaryTypeHandleGenericLookupResult; var metadataLookup = entry as MetadataTypeHandleGenericLookupResult; if (slot < 0 && (necessaryLookup != null || metadataLookup != null)) { for (int i = 0; i < _layout.Length; i++) { if (_layout[i] is TypeHandleGenericLookupResult thOther && (thOther.Type == necessaryLookup?.Type || thOther.Type == metadataLookup?.Type)) { slot = i; return true; } if (_layout[i] is MetadataTypeHandleGenericLookupResult mdOther && mdOther.Type == necessaryLookup?.Type) { slot = i; return true; } } } // 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 (slot < 0) throw new InvalidOperationException($"{OwningMethodOrType}: {entry}"); return true; } public override IEnumerable<GenericLookupResult> Entries { get { return _layout; } } } public sealed class LazilyBuiltDictionaryLayoutNode : DictionaryLayoutNode { private sealed class EntryHashTable : LockFreeReaderHashtable<GenericLookupResult, GenericLookupResult> { protected override bool CompareKeyToValue(GenericLookupResult key, GenericLookupResult value) => Equals(key, value); protected override bool CompareValueToValue(GenericLookupResult value1, GenericLookupResult value2) => Equals(value1, value2); protected override GenericLookupResult CreateValueFromKey(GenericLookupResult key) => key; protected override int GetKeyHashCode(GenericLookupResult key) => key.GetHashCode(); protected override int GetValueHashCode(GenericLookupResult value) => value.GetHashCode(); } private EntryHashTable _entries = new EntryHashTable(); private volatile GenericLookupResult[] _layout; public override bool HasFixedSlots => false; public LazilyBuiltDictionaryLayoutNode(TypeSystemEntity owningMethodOrType) : base(owningMethodOrType) { } public override void EnsureEntry(GenericLookupResult entry) { Debug.Assert(_layout == null, "Trying to add entry but layout already computed"); _entries.AddOrGetExisting(entry); } private void ComputeLayout() { GenericLookupResult[] layout = new GenericLookupResult[_entries.Count]; int index = 0; foreach (GenericLookupResult entry in EntryHashTable.Enumerator.Get(_entries)) { layout[index++] = entry; } var comparer = new GenericLookupResult.Comparer(TypeSystemComparer.Instance); Array.Sort(layout, comparer.Compare); // Only publish after the full layout is computed. Races are fine. _layout = layout; } public override bool TryGetSlotForEntry(GenericLookupResult entry, out int slot) { if (_layout == null) ComputeLayout(); slot = Array.IndexOf(_layout, entry); // We never called EnsureEntry on this during compilation? // This is a fatal error to prevent bad codegen. if (slot < 0) throw new InvalidOperationException($"{OwningMethodOrType}: {entry}"); return true; } public override IEnumerable<GenericLookupResult> Entries { get { if (_layout == null) ComputeLayout(); return _layout; } } public override bool IsEmpty { get { if (_layout == null) ComputeLayout(); return _layout.Length == 0; } } } }