|
// 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 Internal.IL;
using Internal.IL.Stubs;
using Internal.Runtime;
using Internal.Text;
using Internal.TypeSystem;
using Debug = System.Diagnostics.Debug;
using GenericVariance = Internal.Runtime.GenericVariance;
namespace ILCompiler.DependencyAnalysis
{
/// <summary>
/// Given a type, EETypeNode writes an MethodTable data structure in the format expected by the runtime.
///
/// Format of an MethodTable:
///
/// Field Size | Contents
/// ----------------+-----------------------------------
/// UInt32 | Flags field
/// | Flags for: IsValueType, IsCrossModule, HasPointers, IsInterface, IsGeneric, etc ...
/// | EETypeKind (Normal, Array, Pointer type)
/// |
/// | 5 bits near the top are used for enum EETypeElementType to record whether it's back by an Int32, Int16 etc
/// |
/// | The highest/sign bit indicates whether the lower Uint16 contains a number, which represents:
/// | - element type size for arrays,
/// | - char size for strings (normally 2, since .NET uses UTF16 character encoding),
/// | - for generic type definitions it is the number of generic parameters,
/// |
/// | If the sign bit is not set, then the lower Uint16 is used for additional ExtendedFlags
/// |
/// Uint32 | Base size.
/// |
/// [Pointer Size] | Related type. Base type for regular types. Element type for arrays / pointer types.
/// |
/// UInt16 | Number of VTable slots (X)
/// |
/// UInt16 | Number of interfaces implemented by type (Y)
/// |
/// UInt32 | Hash code
/// |
/// X * [Ptr Size] | VTable entries (optional)
/// |
/// Y * [Ptr Size] | Pointers to interface map data structures (optional)
/// |
/// [Relative ptr] | Pointer to containing TypeManager indirection cell
/// |
/// [Relative ptr] | Pointer to writable data
/// |
/// [Relative ptr] | Pointer to finalizer method (optional)
/// |
/// [Relative ptr] | Pointer to optional fields (optional)
/// |
/// [Relative ptr] | Pointer to the generic type definition MethodTable (optional)
/// |
/// [Relative ptr] | Pointer to the generic argument and variance info (optional)
/// </summary>
public partial class EETypeNode : DehydratableObjectNode, IEETypeNode, ISymbolDefinitionNode, ISymbolNodeWithLinkage
{
protected readonly TypeDesc _type;
private readonly WritableDataNode _writableDataNode;
protected bool? _mightHaveInterfaceDispatchMap;
private bool _hasConditionalDependenciesFromMetadataManager;
protected readonly VirtualMethodAnalysisFlags _virtualMethodAnalysisFlags;
[Flags]
protected enum VirtualMethodAnalysisFlags
{
None = 0,
NeedsGvmEntries = 0x0001,
InterestingForDynamicDependencies = 0x0002,
AllFlags = NeedsGvmEntries
| InterestingForDynamicDependencies,
}
public EETypeNode(NodeFactory factory, TypeDesc type)
{
Debug.Assert(!type.IsCanonicalSubtype(CanonicalFormKind.Any) || type == type.ConvertToCanonForm(CanonicalFormKind.Specific));
Debug.Assert(!type.IsCanonicalSubtype(CanonicalFormKind.Any) || !type.IsMdArray); // MDArray doesn't have type loader templates
Debug.Assert(!type.IsGenericParameter);
Debug.Assert(!type.IsRuntimeDeterminedSubtype);
_type = type;
_writableDataNode = !_type.IsCanonicalSubtype(CanonicalFormKind.Any) ? new WritableDataNode(this) : null;
_hasConditionalDependenciesFromMetadataManager = factory.MetadataManager.HasConditionalDependenciesDueToEETypePresence(type);
if (EmitVirtualSlots)
_virtualMethodAnalysisFlags = AnalyzeVirtualMethods(type);
factory.TypeSystemContext.EnsureLoadableType(type);
}
private static VirtualMethodAnalysisFlags AnalyzeVirtualMethods(TypeDesc type)
{
var result = VirtualMethodAnalysisFlags.None;
// Interface EETypes not relevant to virtual method analysis at this time.
if (type.IsInterface)
return result;
DefType defType = type.GetClosestDefType();
foreach (MethodDesc method in defType.GetAllVirtualMethods())
{
// First, check if this type has any GVM that overrides a GVM on a parent type. If that's the case, this makes
// the current type interesting for GVM analysis (i.e. instantiate its overriding GVMs for existing GVMDependenciesNodes
// of the instantiated GVM on the parent types).
if (method.HasInstantiation)
{
result |= VirtualMethodAnalysisFlags.NeedsGvmEntries;
MethodDesc slotDecl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(method);
if (slotDecl != method)
result |= VirtualMethodAnalysisFlags.InterestingForDynamicDependencies;
}
// Early out if we set all the flags we could have set
if ((result & VirtualMethodAnalysisFlags.AllFlags) == VirtualMethodAnalysisFlags.AllFlags)
return result;
}
//
// Check if the type implements any interface, where the method implementations could be on
// base types.
// Example:
// interface IFace {
// void IFaceGVMethod<U>();
// }
// class BaseClass {
// public virtual void IFaceGVMethod<U>() { ... }
// }
// public class DerivedClass : BaseClass, IFace { }
//
foreach (DefType interfaceImpl in defType.RuntimeInterfaces)
{
foreach (MethodDesc method in interfaceImpl.EnumAllVirtualSlots())
{
if (!method.HasInstantiation)
continue;
// We found a GVM on one of the implemented interfaces. Find if the type implements this method.
// (Note, do this comparison against the generic definition of the method, not the specific method instantiation
MethodDesc slotDecl = method.Signature.IsStatic ?
defType.ResolveInterfaceMethodToStaticVirtualMethodOnType(method)
: defType.ResolveInterfaceMethodTarget(method);
if (slotDecl != null)
{
// If the type doesn't introduce this interface method implementation (i.e. the same implementation
// already exists in the base type), do not consider this type interesting for GVM analysis just yet.
//
// We need to limit the number of types that are interesting for GVM analysis at all costs since
// these all will be looked at for every unique generic virtual method call in the program.
// Having a long list of interesting types affects the compilation throughput heavily.
if (slotDecl.OwningType == defType ||
defType.BaseType.ResolveInterfaceMethodTarget(method) != slotDecl)
{
result |= VirtualMethodAnalysisFlags.InterestingForDynamicDependencies
| VirtualMethodAnalysisFlags.NeedsGvmEntries;
}
}
else
{
// The method could be implemented by a default interface method
var resolution = defType.ResolveInterfaceMethodToDefaultImplementationOnType(method, out _);
if (resolution == DefaultInterfaceMethodResolution.DefaultImplementation)
{
result |= VirtualMethodAnalysisFlags.InterestingForDynamicDependencies
| VirtualMethodAnalysisFlags.NeedsGvmEntries;
}
}
// Early out if we set all the flags we could have set
if ((result & VirtualMethodAnalysisFlags.AllFlags) == VirtualMethodAnalysisFlags.AllFlags)
return result;
}
}
return result;
}
protected bool MightHaveInterfaceDispatchMap(NodeFactory factory)
{
if (!_mightHaveInterfaceDispatchMap.HasValue)
{
_mightHaveInterfaceDispatchMap = EmitVirtualSlots && InterfaceDispatchMapNode.MightHaveInterfaceDispatchMap(_type, factory);
}
return _mightHaveInterfaceDispatchMap.Value;
}
protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);
public override bool ShouldSkipEmittingObjectNode(NodeFactory factory)
{
return factory.MetadataTypeSymbol(_type).Marked;
}
public virtual ISymbolNode NodeForLinkage(NodeFactory factory)
{
return factory.NecessaryTypeSymbol(_type);
}
public TypeDesc Type => _type;
protected override ObjectNodeSection GetDehydratedSection(NodeFactory factory)
{
if (factory.Target.IsWindows)
return ObjectNodeSection.ReadOnlyDataSection;
else
return ObjectNodeSection.DataSection;
}
public int MinimumObjectSize => EETypeBuilderHelpers.GetMinimumObjectSize(_type.Context);
protected virtual bool EmitVirtualSlots => false;
protected virtual bool IsReflectionVisible => false;
public override bool InterestingForDynamicDependencyAnalysis
=> (_virtualMethodAnalysisFlags & VirtualMethodAnalysisFlags.InterestingForDynamicDependencies) != 0;
public override bool StaticDependenciesAreComputed => true;
public virtual void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
{
sb.Append(nameMangler.NodeMangler.MethodTable(_type));
}
int ISymbolNode.Offset => 0;
int ISymbolDefinitionNode.Offset => GCDescSize;
public override bool IsShareable => IsTypeNodeShareable(_type);
private bool CanonFormTypeMayExist
{
get
{
if (_type.IsArrayTypeWithoutGenericInterfaces())
return false;
if (!_type.Context.SupportsCanon)
return false;
// If type is already in canon form, a canonically equivalent type cannot exist
if (_type.IsCanonicalSubtype(CanonicalFormKind.Any))
return false;
// Attempt to convert to canon. If the type changes, then the CanonForm exists
return (_type.ConvertToCanonForm(CanonicalFormKind.Specific) != _type);
}
}
public override bool HasConditionalStaticDependencies
{
get
{
// If the type is can be converted to some interesting canon type, and this is the non-constructed variant of an MethodTable
// we may need to trigger the fully constructed type to exist to make the behavior of the type consistent
// in reflection and generic template expansion scenarios
if (CanonFormTypeMayExist)
{
return true;
}
// If the type implements at least one interface, calls against that interface could result in this type's
// implementation being used.
// It might also be necessary for casting purposes.
if (_type.RuntimeInterfaces.Length > 0)
return true;
if (IsReflectionVisible && _hasConditionalDependenciesFromMetadataManager)
return true;
if (!EmitVirtualSlots)
return false;
// Since the vtable is dependency driven, generate conditional static dependencies for
// all possible vtable entries.
//
// The conditional dependencies conditionally add the implementation of the virtual method
// if the virtual method is used.
//
// We walk the inheritance chain because abstract bases would only add a "tentative"
// method body of the implementation that can be trimmed away if no other type uses it.
DefType currentType = _type.GetClosestDefType();
while (currentType != null)
{
if (currentType == _type || (currentType is MetadataType mdType && mdType.IsAbstract))
{
foreach (var method in currentType.GetAllVirtualMethods())
{
// Abstract methods don't have a body associated with it so there's no conditional
// dependency to add.
// Generic virtual methods are tracked by an orthogonal mechanism.
if (!method.IsAbstract && !method.HasInstantiation)
return true;
}
}
currentType = currentType.BaseType;
}
return false;
}
}
public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory)
{
List<CombinedDependencyListEntry> result = new List<CombinedDependencyListEntry>();
if (IsReflectionVisible)
{
factory.MetadataManager.GetConditionalDependenciesDueToEETypePresence(ref result, factory, _type, allocated: EmitVirtualSlots);
if (!_type.IsCanonicalSubtype(CanonicalFormKind.Any))
{
foreach (DefType iface in _type.RuntimeInterfaces)
{
var ifaceDefinition = (DefType)iface.GetTypeDefinition();
result.Add(new CombinedDependencyListEntry(
GetInterfaceTypeNode(factory, iface),
factory.InterfaceUse(ifaceDefinition),
"Interface definition was visible"));
}
}
}
IEETypeNode maximallyConstructableType = factory.MaximallyConstructableType(_type);
if (maximallyConstructableType != this)
{
// MethodTable upgrading from necessary to constructed if some template instantiation exists that matches up
// This ensures we don't end up having two EETypes in the system (one is this necessary type, and another one
// that was dynamically created at runtime).
if (CanonFormTypeMayExist)
{
result.Add(new CombinedDependencyListEntry(maximallyConstructableType, factory.MaximallyConstructableType(_type.ConvertToCanonForm(CanonicalFormKind.Specific)), "Trigger full type generation if canonical form exists"));
}
return result;
}
TypeDesc canonOwningType = _type.ConvertToCanonForm(CanonicalFormKind.Specific);
if (_type.IsDefType && _type != canonOwningType)
{
result.Add(new CombinedDependencyListEntry(
factory.GenericStaticBaseInfo((MetadataType)_type),
factory.NativeLayout.TemplateTypeLayout(canonOwningType),
"Information about static bases for type with template"));
}
if (!EmitVirtualSlots)
return result;
DefType defType = _type.GetClosestDefType();
// If we're producing a full vtable, none of the dependencies are conditional.
if (!factory.VTable(defType).HasKnownVirtualMethodUse)
{
if (!defType.IsInterface)
{
bool isAbstractType = ((MetadataType)defType).IsAbstract;
foreach (MethodDesc decl in defType.EnumAllVirtualSlots())
{
// Generic virtual methods are tracked by an orthogonal mechanism.
if (decl.HasInstantiation)
continue;
MethodDesc impl = defType.FindVirtualFunctionTargetMethodOnObjectType(decl);
bool implOwnerIsAbstract = ((MetadataType)impl.OwningType).IsAbstract;
// We add a conditional dependency in two situations:
// 1. The implementation is on this type. This is pretty obvious.
// 2. The implementation comes from an abstract base type. We do this
// because abstract types only request a TentativeMethodEntrypoint of the implementation.
// The actual method body of this entrypoint might still be trimmed away.
// We don't need to do this for implementations from non-abstract bases since
// non-abstract types will create a hard conditional reference to their virtual
// method implementations.
//
// We also skip abstract methods since they don't have a body to refer to.
if ((impl.OwningType == defType || implOwnerIsAbstract) && !impl.IsAbstract)
{
MethodDesc canonImpl = impl.GetCanonMethodTarget(CanonicalFormKind.Specific);
// If this is an abstract type, only request a tentative entrypoint (whose body
// might just be stubbed out). This lets us avoid generating method bodies for
// virtual method on abstract types that are overridden in all their children.
//
// We don't do this if the method can be placed in the sealed vtable since
// those can never be overridden by children anyway.
bool canUseTentativeMethod = isAbstractType
&& !decl.CanMethodBeInSealedVTable(factory)
&& factory.CompilationModuleGroup.AllowVirtualMethodOnAbstractTypeOptimization(canonImpl);
IMethodNode implNode = canUseTentativeMethod ?
factory.TentativeMethodEntrypoint(canonImpl, impl.OwningType.IsValueType) :
factory.MethodEntrypoint(canonImpl, impl.OwningType.IsValueType);
result.Add(new CombinedDependencyListEntry(implNode, factory.VirtualMethodUse(decl), "Virtual method"));
result.Add(new CombinedDependencyListEntry(
factory.AddressTakenMethodEntrypoint(canonImpl, impl.OwningType.IsValueType),
factory.DelegateTargetVirtualMethod(decl.GetCanonMethodTarget(CanonicalFormKind.Specific)), "Slot is a delegate target"));
}
if (impl.OwningType == defType)
{
factory.MetadataManager.NoteOverridingMethod(decl, impl);
}
factory.MetadataManager.GetDependenciesForOverridingMethod(ref result, factory, decl, impl);
}
}
Debug.Assert(
_type == defType ||
((System.Collections.IStructuralEquatable)defType.RuntimeInterfaces).Equals(_type.RuntimeInterfaces,
EqualityComparer<DefType>.Default));
// Interfaces don't have vtables and we don't need to track their instance method slot use.
// The only exception are those interfaces that provide IDynamicInterfaceCastable implementations;
// those have slots and we dispatch on them.
bool needsDependenciesForInstanceInterfaceMethodImpls = !defType.IsInterface
|| ((MetadataType)defType).IsDynamicInterfaceCastableImplementation();
// Add conditional dependencies for interface methods the type implements. For example, if the type T implements
// interface IFoo which has a method M1, add a dependency on T.M1 dependent on IFoo.M1 being called, since it's
// possible for any IFoo object to actually be an instance of T.
DefType defTypeDefinition = (DefType)defType.GetTypeDefinition();
DefType[] defTypeRuntimeInterfaces = defType.RuntimeInterfaces;
DefType[] defTypeDefinitionRuntimeInterfaces = defTypeDefinition.RuntimeInterfaces;
Debug.Assert(defTypeDefinitionRuntimeInterfaces.Length == defTypeRuntimeInterfaces.Length);
for (int interfaceIndex = 0; interfaceIndex < defTypeRuntimeInterfaces.Length; interfaceIndex++)
{
DefType interfaceType = defTypeRuntimeInterfaces[interfaceIndex];
DefType definitionInterfaceType = defTypeDefinitionRuntimeInterfaces[interfaceIndex];
Debug.Assert(interfaceType.IsInterface);
bool isVariantInterfaceImpl = VariantInterfaceMethodUseNode.IsVariantInterfaceImplementation(factory, _type, interfaceType);
foreach (MethodDesc interfaceMethod in interfaceType.EnumAllVirtualSlots())
{
// Generic virtual methods are tracked by an orthogonal mechanism.
if (interfaceMethod.HasInstantiation)
continue;
bool isStaticInterfaceMethod = interfaceMethod.Signature.IsStatic;
if (!isStaticInterfaceMethod && !needsDependenciesForInstanceInterfaceMethodImpls)
continue;
MethodDesc interfaceMethodDefinition = interfaceMethod;
if (interfaceType != definitionInterfaceType)
interfaceMethodDefinition = factory.TypeSystemContext.GetMethodForInstantiatedType(interfaceMethodDefinition.GetTypicalMethodDefinition(), (InstantiatedType)definitionInterfaceType);
MethodDesc implMethod = isStaticInterfaceMethod ?
defTypeDefinition.ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethodDefinition) :
defTypeDefinition.ResolveInterfaceMethodToVirtualMethodOnType(interfaceMethodDefinition);
if (implMethod != null)
{
TypeDesc implType = defType;
while (!implType.HasSameTypeDefinition(implMethod.OwningType))
implType = implType.BaseType;
if (!implType.IsTypeDefinition)
implMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(implMethod.GetTypicalMethodDefinition(), (InstantiatedType)implType);
if (isStaticInterfaceMethod)
{
Debug.Assert(!implMethod.IsVirtual);
MethodDesc defaultIntfMethod = implMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);
// If the interface method is used virtually, the implementation body is used
result.Add(new CombinedDependencyListEntry(factory.MethodEntrypoint(defaultIntfMethod), factory.VirtualMethodUse(interfaceMethod), "Interface method"));
// If the interface method is virtual delegate target, the implementation is address taken
result.Add(new CombinedDependencyListEntry(
factory.AddressTakenMethodEntrypoint(defaultIntfMethod),
factory.DelegateTargetVirtualMethod(interfaceMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)), "Interface slot is delegate target"));
}
else
{
// If the interface method is used virtually, the slot is used virtually
result.Add(new CombinedDependencyListEntry(factory.VirtualMethodUse(implMethod), factory.VirtualMethodUse(interfaceMethod), "Interface method"));
// If the interface method is virtual delegate target, the slot is virtual delegate target
result.Add(new CombinedDependencyListEntry(
factory.DelegateTargetVirtualMethod(implMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)),
factory.DelegateTargetVirtualMethod(interfaceMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)),
"Interface slot is delegate target"));
}
// If any of the implemented interfaces have variance, calls against compatible interface methods
// could result in interface methods of this type being used (e.g. IEnumerable<object>.GetEnumerator()
// can dispatch to an implementation of IEnumerable<string>.GetEnumerator()).
if (isVariantInterfaceImpl)
{
MethodDesc typicalInterfaceMethod = interfaceMethod.GetTypicalMethodDefinition();
object implMethodUseNode = isStaticInterfaceMethod ?
factory.CanonicalEntrypoint(implMethod) : factory.VirtualMethodUse(implMethod);
result.Add(new CombinedDependencyListEntry(implMethodUseNode, factory.VariantInterfaceMethodUse(typicalInterfaceMethod), "Interface method"));
result.Add(new CombinedDependencyListEntry(factory.VirtualMethodUse(interfaceMethod), factory.VariantInterfaceMethodUse(typicalInterfaceMethod), "Interface method"));
}
TypeSystemEntity origin = (implMethod.OwningType != defType) ? defType : null;
factory.MetadataManager.NoteOverridingMethod(interfaceMethod, implMethod, origin);
factory.MetadataManager.GetDependenciesForOverridingMethod(ref result, factory, interfaceMethod, implMethod);
}
else
{
// Is the implementation provided by a default interface method?
// If so, add a dependency on the entrypoint directly since nobody else is going to do that
// (interface types have an empty vtable, modulo their generic dictionary).
var resolution = defTypeDefinition.ResolveInterfaceMethodToDefaultImplementationOnType(interfaceMethodDefinition, out implMethod);
if (resolution == DefaultInterfaceMethodResolution.DefaultImplementation)
{
DefType providingInterfaceDefinitionType = (DefType)implMethod.OwningType;
implMethod = implMethod.InstantiateSignature(defType.Instantiation, Instantiation.Empty);
MethodDesc defaultIntfMethod = implMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);
if (!isStaticInterfaceMethod && defaultIntfMethod.IsCanonicalMethod(CanonicalFormKind.Any))
{
// Canonical instance default methods need to go through a thunk that adds the right generic context
defaultIntfMethod = factory.TypeSystemContext.GetDefaultInterfaceMethodImplementationThunk(defaultIntfMethod, defType.ConvertToCanonForm(CanonicalFormKind.Specific), providingInterfaceDefinitionType, out int providingInterfaceIndex);
// The above thunk will index into interface list to find the right context. Make sure to keep all interfaces prior to this one
for (int i = 0; i <= providingInterfaceIndex; i++)
{
result.Add(new CombinedDependencyListEntry(
factory.InterfaceUse(defTypeRuntimeInterfaces[i].GetTypeDefinition()),
factory.VirtualMethodUse(interfaceMethod), "Interface with shared default methods folows this"));
}
}
result.Add(new CombinedDependencyListEntry(factory.MethodEntrypoint(defaultIntfMethod), factory.VirtualMethodUse(interfaceMethod), "Interface method"));
result.Add(new CombinedDependencyListEntry(
factory.AddressTakenMethodEntrypoint(defaultIntfMethod),
factory.DelegateTargetVirtualMethod(interfaceMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)),
"Slot is delegate target"));
factory.MetadataManager.NoteOverridingMethod(interfaceMethod, implMethod);
factory.MetadataManager.GetDependenciesForOverridingMethod(ref result, factory, interfaceMethod, implMethod);
}
}
}
}
}
return result;
}
public static bool IsTypeNodeShareable(TypeDesc type)
{
return type.IsParameterizedType || type.IsFunctionPointer || type is InstantiatedType;
}
internal static bool MethodHasNonGenericILMethodBody(MethodDesc method)
{
// Generic methods have their own generic dictionaries
if (method.HasInstantiation)
return false;
// Abstract methods don't have a body
if (method.IsAbstract)
return false;
// PInvoke methods are not permitted on generic types,
// but let's not crash the compilation because of that.
if (method.IsPInvoke)
return false;
// NativeAOT can generate method bodies for these no matter what (worst case
// they'll be throwing). We don't want to take the "return false" code path because
// delegate methods fall into the runtime implemented category on NativeAOT, but we
// just treat them like regular method bodies.
return true;
}
protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)
{
DependencyList dependencies = new DependencyList();
if (_type.IsInterface)
dependencies.Add(factory.InterfaceUse(_type.GetTypeDefinition()), "Interface is used");
// Array types that don't have generic interface methods can be created out of thin air
// at runtime by the type loader. We should never emit non-constructed forms of these MethodTables.
// There's similar logic for generic types, but that one is a conditional dependency conditioned
// on the presence of the type loader template for the canonical form of the type.
if (_type.IsArrayTypeWithoutGenericInterfaces())
{
IEETypeNode maximallyConstructableType = factory.MaximallyConstructableType(_type);
if (maximallyConstructableType != this)
{
dependencies.Add(maximallyConstructableType, "Type is template-loadable");
}
}
if (EmitVirtualSlots)
{
if (!_type.IsArrayTypeWithoutGenericInterfaces())
{
// Sealed vtables have relative pointers, so to minimize size, we build sealed vtables for the canonical types
dependencies.Add(new DependencyListEntry(factory.SealedVTable(_type.ConvertToCanonForm(CanonicalFormKind.Specific)), "Sealed Vtable"));
}
// Also add the un-normalized vtable slices of implemented interfaces.
// This is important to do in the scanning phase so that the compilation phase can find
// vtable information for things like IEnumerator<List<__Canon>>.
foreach (TypeDesc intface in _type.RuntimeInterfaces)
dependencies.Add(factory.VTable(intface), "Interface vtable slice");
// Generated type contains generic virtual methods that will get added to the GVM tables
if ((_virtualMethodAnalysisFlags & VirtualMethodAnalysisFlags.NeedsGvmEntries) != 0)
{
TypeDesc canonicalType = _type.ConvertToCanonForm(CanonicalFormKind.Specific);
if (canonicalType != _type)
dependencies.Add(factory.ConstructedTypeSymbol(canonicalType), "Type with generic virtual methods");
}
}
if (factory.CompilationModuleGroup.PresenceOfEETypeImpliesAllMethodsOnType(_type))
{
if (_type.IsArray || _type.IsDefType)
{
// If the compilation group wants this type to be fully promoted, ensure that all non-generic methods of the
// type are generated.
// This may be done for several reasons:
// - The MethodTable may be going to be COMDAT folded with other EETypes generated in a different object file
// This means their generic dictionaries need to have identical contents. The only way to achieve that is
// by generating the entries for all methods that contribute to the dictionary, and sorting the dictionaries.
// - The generic type may be imported into another module, in which case the generic dictionary imported
// must represent all of the methods, as the set of used methods cannot be known at compile time
// - As a matter of policy, the type and its methods may be exported for use in another module. The policy
// may wish to specify that if a type is to be placed into a shared module, all of the methods associated with
// it should be also be exported.
foreach (var method in _type.GetClosestDefType().ConvertToCanonForm(CanonicalFormKind.Specific).GetAllMethodsAndAsyncVariants())
{
if (!MethodHasNonGenericILMethodBody(method))
continue;
dependencies.Add(factory.MethodEntrypoint(method.GetCanonMethodTarget(CanonicalFormKind.Specific)),
"Ensure all methods on type due to CompilationModuleGroup policy");
}
}
}
if (!ConstructedEETypeNode.CreationAllowed(_type))
{
// If necessary MethodTable is the highest load level for this type, ask the metadata manager
// if we have any dependencies due to presence of the EEType.
factory.MetadataManager.GetDependenciesDueToEETypePresence(ref dependencies, factory, _type);
// If necessary MethodTable is the highest load level, consider this a module use
if (_type is MetadataType mdType)
ModuleUseBasedDependencyAlgorithm.AddDependenciesDueToModuleUse(ref dependencies, factory, mdType.Module);
}
if (_type.IsFunctionPointer)
FunctionPointerMapNode.GetHashtableDependencies(ref dependencies, factory, (FunctionPointerType)_type);
return dependencies;
}
protected override ObjectData GetDehydratableData(NodeFactory factory, bool relocsOnly)
{
ObjectDataBuilder objData = new ObjectDataBuilder(factory, relocsOnly);
objData.RequireInitialPointerAlignment();
objData.AddSymbol(this);
OutputGCDesc(ref objData);
OutputFlags(factory, ref objData, relocsOnly);
objData.EmitInt(BaseSize);
OutputRelatedType(factory, ref objData);
// Number of vtable slots will be only known later. Reseve the bytes for it.
var vtableSlotCountReservation = objData.ReserveShort();
// Number of interfaces will only be known later. Reserve the bytes for it.
var interfaceCountReservation = objData.ReserveShort();
objData.EmitInt(_type.GetHashCode());
if (EmitVirtualSlots)
{
// Emit VTable
Debug.Assert(objData.CountBytes - ((ISymbolDefinitionNode)this).Offset == GetVTableOffset(objData.TargetPointerSize));
SlotCounter virtualSlotCounter = SlotCounter.BeginCounting(ref /* readonly */ objData);
OutputVirtualSlots(factory, ref objData, _type, _type, _type, relocsOnly);
// Update slot count
int numberOfVtableSlots = virtualSlotCounter.CountSlots(ref /* readonly */ objData);
objData.EmitShort(vtableSlotCountReservation, checked((short)numberOfVtableSlots));
}
else
{
// If we're not emitting any slots, the number of slots is zero.
objData.EmitShort(vtableSlotCountReservation, 0);
}
// Emit interface map
SlotCounter interfaceSlotCounter = SlotCounter.BeginCounting(ref /* readonly */ objData);
if (!relocsOnly)
{
OutputInterfaceMap(factory, ref objData);
}
// Update slot count
int numberOfInterfaceSlots = interfaceSlotCounter.CountSlots(ref /* readonly */ objData);
objData.EmitShort(interfaceCountReservation, checked((short)numberOfInterfaceSlots));
OutputTypeManagerIndirection(factory, ref objData);
OutputWritableData(factory, ref objData);
OutputDispatchMap(factory, ref objData);
OutputFinalizerMethod(factory, ref objData);
OutputSealedVTable(factory, relocsOnly, ref objData);
OutputGenericInstantiationDetails(factory, ref objData);
OutputFunctionPointerParameters(factory, ref objData);
return objData.ToObjectData();
}
/// <summary>
/// Returns the offset within an MethodTable of the beginning of VTable entries
/// </summary>
/// <param name="pointerSize">The size of a pointer in bytes in the target architecture</param>
public static int GetVTableOffset(int pointerSize)
{
return 16 + pointerSize;
}
protected virtual int GCDescSize => 0;
protected virtual void OutputGCDesc(ref ObjectDataBuilder builder)
{
// Non-constructed EETypeNodes get no GC Desc
Debug.Assert(GCDescSize == 0);
}
private void OutputFlags(NodeFactory factory, ref ObjectDataBuilder objData, bool relocsOnly)
{
uint flags = EETypeBuilderHelpers.ComputeFlags(_type);
if (_type.GetTypeDefinition() == factory.TypeSystemContext.ArrayOfTEnumeratorType)
{
// Generic array enumerators use special variance rules recognized by the runtime
flags |= (uint)EETypeFlags.GenericVarianceFlag;
}
if (factory.TypeSystemContext.IsGenericArrayInterfaceType(_type))
{
// Runtime casting logic relies on all interface types implemented on arrays
// to have the variant flag set (even if all the arguments are non-variant).
// This supports e.g. casting uint[] to ICollection<int>
flags |= (uint)EETypeFlags.GenericVarianceFlag;
}
if (EmitVirtualSlots && !_type.IsArrayTypeWithoutGenericInterfaces())
{
SealedVTableNode sealedVTable = factory.SealedVTable(_type.ConvertToCanonForm(CanonicalFormKind.Specific));
if (sealedVTable.BuildSealedVTableSlots(factory, relocsOnly) && sealedVTable.NumSealedVTableEntries > 0)
flags |= (uint)EETypeFlags.HasSealedVTableEntriesFlag;
}
if (MightHaveInterfaceDispatchMap(factory))
{
flags |= (uint)EETypeFlags.HasDispatchMap;
}
if (_type.IsArray || _type.IsString)
{
flags |= (uint)EETypeFlags.HasComponentSizeFlag;
}
//
// output ComponentSize or FlagsEx
//
if (_type.IsArray)
{
TypeDesc elementType = ((ArrayType)_type).ElementType;
int elementSize = elementType.GetElementSize().AsInt;
// We validated that this will fit the short when the node was constructed. No need for nice messages.
flags |= (uint)checked((ushort)elementSize);
}
else if (_type.IsString)
{
flags |= StringComponentSize.Value;
}
else
{
ushort flagsEx = EETypeBuilderHelpers.ComputeFlagsEx(_type);
flags |= flagsEx;
}
objData.EmitUInt(flags);
}
protected virtual int BaseSize
{
get
{
return EETypeBuilderHelpers.ComputeBaseSize(_type);
}
}
protected virtual ISymbolNode GetBaseTypeNode(NodeFactory factory)
{
return _type.BaseType != null ? factory.NecessaryTypeSymbol(_type.BaseType.NormalizeInstantiation()) : null;
}
protected virtual FrozenRuntimeTypeNode GetFrozenRuntimeTypeNode(NodeFactory factory)
{
Debug.Assert(!_type.IsCanonicalSubtype(CanonicalFormKind.Any));
return factory.SerializedNecessaryRuntimeTypeObject(_type);
}
protected virtual ISymbolNode GetNonNullableValueTypeArrayElementTypeNode(NodeFactory factory)
{
return factory.NecessaryTypeSymbol(((ArrayType)_type).ElementType);
}
private ISymbolNode GetRelatedTypeNode(NodeFactory factory)
{
ISymbolNode relatedTypeNode = null;
if (_type.IsParameterizedType)
{
var parameterType = ((ParameterizedType)_type).ParameterType;
if (_type.IsArray && parameterType.IsValueType && !parameterType.IsNullable)
{
// This might be a constructed type symbol. There are APIs on Array that allow allocating element
// types through runtime magic ("((Array)new NeverAllocated[1]).GetValue(0)" or IEnumerable) and we don't have
// visibility into that. Conservatively assume element types of constructed arrays are also constructed.
relatedTypeNode = GetNonNullableValueTypeArrayElementTypeNode(factory);
}
else
{
relatedTypeNode = factory.NecessaryTypeSymbol(parameterType);
}
}
else if (_type.IsFunctionPointer)
{
relatedTypeNode = factory.NecessaryTypeSymbol(((FunctionPointerType)_type).Signature.ReturnType);
}
else
{
TypeDesc baseType = _type.BaseType;
if (baseType != null)
{
relatedTypeNode = GetBaseTypeNode(factory);
}
}
return relatedTypeNode;
}
protected virtual void OutputRelatedType(NodeFactory factory, ref ObjectDataBuilder objData)
{
ISymbolNode relatedTypeNode = GetRelatedTypeNode(factory);
if (relatedTypeNode != null)
{
objData.EmitPointerReloc(relatedTypeNode);
}
else
{
objData.EmitZeroPointer();
}
}
private void OutputVirtualSlots(NodeFactory factory, ref ObjectDataBuilder objData, TypeDesc implType, TypeDesc declType, TypeDesc templateType, bool relocsOnly)
{
Debug.Assert(EmitVirtualSlots);
declType = declType.GetClosestDefType();
templateType = templateType.ConvertToCanonForm(CanonicalFormKind.Specific);
var baseType = declType.BaseType;
if (baseType != null)
{
Debug.Assert(templateType.BaseType != null);
OutputVirtualSlots(factory, ref objData, implType, baseType, templateType.BaseType, relocsOnly);
}
//
// In the universal canonical types case, we could have base types in the hierarchy that are partial universal canonical types.
// The presence of these types could cause incorrect vtable layouts, so we need to fully canonicalize them and walk the
// hierarchy of the template type of the original input type to detect these cases.
//
// Exmaple: we begin with Derived<__UniversalCanon> and walk the template hierarchy:
//
// class Derived<T> : Middle<T, MyStruct> { } // -> Template is Derived<__UniversalCanon> and needs a dictionary slot
// // -> Basetype tempalte is Middle<__UniversalCanon, MyStruct>. It's a partial
// Universal canonical type, so we need to fully canonicalize it.
//
// class Middle<T, U> : Base<U> { } // -> Template is Middle<__UniversalCanon, __UniversalCanon> and needs a dictionary slot
// // -> Basetype template is Base<__UniversalCanon>
//
// class Base<T> { } // -> Template is Base<__UniversalCanon> and needs a dictionary slot.
//
// If we had not fully canonicalized the Middle class template, we would have ended up with Base<MyStruct>, which does not need
// a dictionary slot, meaning we would have created a vtable layout that the runtime does not expect.
//
// The generic dictionary pointer occupies the first slot of each type vtable slice
if (declType.HasGenericDictionarySlot() || templateType.HasGenericDictionarySlot())
{
// All generic interface types have a dictionary slot, but only some of them have an actual dictionary.
bool isInterfaceWithAnEmptySlot = declType.IsInterface &&
declType.ConvertToCanonForm(CanonicalFormKind.Specific) == declType;
// Note: Canonical type instantiations always have a generic dictionary vtable slot, but it's empty
if (declType.IsCanonicalSubtype(CanonicalFormKind.Any)
|| factory.LazyGenericsPolicy.UsesLazyGenerics(declType)
|| isInterfaceWithAnEmptySlot)
{
objData.EmitZeroPointer();
}
else
{
TypeGenericDictionaryNode dictionaryNode = factory.TypeGenericDictionary(declType);
DictionaryLayoutNode layoutNode = dictionaryNode.GetDictionaryLayout(factory);
// Don't bother emitting a reloc to an empty dictionary. We'll only know whether the dictionary is
// empty at final object emission time, so don't ask if we're not emitting yet.
if (!relocsOnly && layoutNode.IsEmpty)
objData.EmitZeroPointer();
else
objData.EmitPointerReloc(dictionaryNode);
}
}
VTableSliceNode declVTable = factory.VTable(declType);
// It's only okay to touch the actual list of slots if we're in the final emission phase
// or the vtable is not built lazily.
if (relocsOnly && !declVTable.HasKnownVirtualMethodUse)
return;
// Interface types don't place anything else in their physical vtable.
// Interfaces have logical slots for their methods but since they're all abstract, they would be zero.
// We place default implementations of interface methods into the vtable of the interface-implementing
// type, pretending there was an extra virtual slot.
if (_type.IsInterface)
return;
bool isAsyncStateMachineValueType = implType.IsValueType
&& factory.TypeSystemContext.IsAsyncStateMachineType((MetadataType)implType);
// Actual vtable slots follow
IReadOnlyList<MethodDesc> virtualSlots = declVTable.Slots;
for (int i = 0; i < virtualSlots.Count; i++)
{
MethodDesc declMethod = virtualSlots[i];
// Object.Finalize shouldn't get a virtual slot. Finalizer is stored in an optional field
// instead: most MethodTable don't have a finalizer, but all EETypes contain Object's vtable.
// This lets us save a pointer (+reloc) on most EETypes.
Debug.Assert(!declType.IsObject || !declMethod.Name.SequenceEqual("Finalize"u8));
// No generic virtual methods can appear in the vtable!
Debug.Assert(!declMethod.HasInstantiation);
// Final NewSlot methods cannot be overridden, and therefore can be placed in the sealed-vtable to reduce the size of the vtable
// of this type and any type that inherits from it.
if (declMethod.CanMethodBeInSealedVTable(factory) && !declType.IsArrayTypeWithoutGenericInterfaces())
continue;
if (!declVTable.IsSlotUsed(declMethod))
{
objData.EmitZeroPointer();
continue;
}
MethodDesc implMethod = implType.GetClosestDefType().FindVirtualFunctionTargetMethodOnObjectType(declMethod);
bool shouldEmitImpl = !implMethod.IsAbstract;
// We do a size optimization that removes support for built-in ValueType Equals/GetHashCode
// Null out the vtable slot associated with built-in support to catch if it ever becomes illegal.
// We also null out Equals/GetHashCode - that's just a marginal size/startup optimization.
if (isAsyncStateMachineValueType)
{
if ((declType.IsObject && (declMethod.Name.SequenceEqual("Equals"u8) || declMethod.Name.SequenceEqual("GetHashCode"u8)) && implMethod.OwningType.IsWellKnownType(WellKnownType.ValueType))
|| (declType.IsWellKnownType(WellKnownType.ValueType) && declMethod.Name.SequenceEqual(GetFieldHelperMethodOverride.MetadataName)))
{
shouldEmitImpl = false;
}
}
if (shouldEmitImpl)
{
MethodDesc canonImplMethod = implMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);
// If the type we're generating now is abstract, and the implementation comes from an abstract type,
// only use a tentative method entrypoint that can have its body replaced by a throwing stub
// if no "hard" reference to that entrypoint exists in the program.
// This helps us to eliminate method bodies for virtual methods on abstract types that are fully overridden
// in the children of that abstract type.
bool canUseTentativeEntrypoint = implType is MetadataType mdImplType && mdImplType.IsAbstract && !mdImplType.IsInterface
&& implMethod.OwningType is MetadataType mdImplMethodType && mdImplMethodType.IsAbstract
&& factory.CompilationModuleGroup.AllowVirtualMethodOnAbstractTypeOptimization(canonImplMethod);
IMethodNode implSymbol;
if (canUseTentativeEntrypoint)
implSymbol = factory.TentativeMethodEntrypoint(canonImplMethod, implMethod.OwningType.IsValueType);
else if (factory.DelegateTargetVirtualMethod(declMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)).Marked)
implSymbol = factory.AddressTakenMethodEntrypoint(canonImplMethod, implMethod.OwningType.IsValueType);
else
implSymbol = factory.MethodEntrypoint(canonImplMethod, implMethod.OwningType.IsValueType);
objData.EmitPointerReloc(implSymbol);
}
else
{
objData.EmitZeroPointer();
}
}
}
protected virtual IEETypeNode GetInterfaceTypeNode(NodeFactory factory, TypeDesc interfaceType)
{
return factory.NecessaryTypeSymbol(interfaceType.NormalizeInstantiation());
}
private void OutputInterfaceMap(NodeFactory factory, ref ObjectDataBuilder objData)
{
if (_type.IsCanonicalSubtype(CanonicalFormKind.Any))
{
// Canonical types (type loader templates) do not generate an interface list - we build
// one for the loaded type at runtime dynamically from template data.
foreach (DefType itf in _type.RuntimeInterfaces)
{
// If the interface was optimized away, skip it
if (factory.InterfaceUse(itf.GetTypeDefinition()).Marked)
{
// Interface omitted for canonical instantiations (constructed at runtime for dynamic types from the native layout info)
objData.EmitZeroPointer();
}
}
}
else
{
foreach (var itf in _type.RuntimeInterfaces)
{
IEETypeNode interfaceTypeNode = GetInterfaceTypeNode(factory, itf);
// Only emit interfaces that were not optimized away.
if (interfaceTypeNode.Marked)
{
objData.EmitPointerReloc(interfaceTypeNode);
}
}
}
}
private void OutputFinalizerMethod(NodeFactory factory, ref ObjectDataBuilder objData)
{
if (_type.HasFinalizer)
{
MethodDesc finalizerMethod = _type.GetFinalizer();
MethodDesc canonFinalizerMethod = finalizerMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);
if (factory.Target.SupportsRelativePointers)
objData.EmitReloc(factory.MethodEntrypoint(canonFinalizerMethod), RelocType.IMAGE_REL_BASED_RELPTR32);
else
objData.EmitPointerReloc(factory.MethodEntrypoint(canonFinalizerMethod));
}
}
protected void OutputTypeManagerIndirection(NodeFactory factory, ref ObjectDataBuilder objData)
{
if (factory.Target.SupportsRelativePointers)
objData.EmitReloc(factory.TypeManagerIndirection, RelocType.IMAGE_REL_BASED_RELPTR32);
else
objData.EmitPointerReloc(factory.TypeManagerIndirection);
}
protected void OutputWritableData(NodeFactory factory, ref ObjectDataBuilder objData)
{
if (_writableDataNode != null)
{
if (factory.Target.SupportsRelativePointers)
objData.EmitReloc(_writableDataNode, RelocType.IMAGE_REL_BASED_RELPTR32);
else
objData.EmitPointerReloc(_writableDataNode);
}
else
{
if (factory.Target.SupportsRelativePointers)
objData.EmitInt(0);
else
objData.EmitZeroPointer();
}
}
private void OutputSealedVTable(NodeFactory factory, bool relocsOnly, ref ObjectDataBuilder objData)
{
if (EmitVirtualSlots && !_type.IsArrayTypeWithoutGenericInterfaces())
{
// Sealed vtables have relative pointers, so to minimize size, we build sealed vtables for the canonical types
SealedVTableNode sealedVTable = factory.SealedVTable(_type.ConvertToCanonForm(CanonicalFormKind.Specific));
if (sealedVTable.BuildSealedVTableSlots(factory, relocsOnly) && sealedVTable.NumSealedVTableEntries > 0)
{
if (factory.Target.SupportsRelativePointers)
objData.EmitReloc(sealedVTable, RelocType.IMAGE_REL_BASED_RELPTR32);
else
objData.EmitPointerReloc(sealedVTable);
}
}
}
protected void OutputGenericInstantiationDetails(NodeFactory factory, ref ObjectDataBuilder objData)
{
if (_type.HasInstantiation)
{
if (!_type.IsTypeDefinition)
{
IEETypeNode typeDefNode = IsReflectionVisible ?
factory.MetadataTypeSymbol(_type.GetTypeDefinition()) : factory.NecessaryTypeSymbol(_type.GetTypeDefinition());
if (factory.Target.SupportsRelativePointers)
objData.EmitReloc(typeDefNode, RelocType.IMAGE_REL_BASED_RELPTR32);
else
objData.EmitPointerReloc(typeDefNode);
ISymbolNode compositionNode;
if (IsReflectionVisible
&& factory.MetadataManager.IsTypeInstantiationReflectionVisible(_type))
{
compositionNode = _type.Instantiation.Length > 1
? factory.ConstructedGenericComposition(_type.Instantiation)
: factory.MaximallyConstructableType(_type.Instantiation[0]);
}
else
{
compositionNode = _type.Instantiation.Length > 1
? factory.GenericComposition(_type.Instantiation)
: factory.NecessaryTypeSymbol(_type.Instantiation[0]);
}
if (factory.Target.SupportsRelativePointers)
objData.EmitReloc(compositionNode, RelocType.IMAGE_REL_BASED_RELPTR32);
else
objData.EmitPointerReloc(compositionNode);
}
else
{
GenericVarianceDetails details;
if (_type == factory.TypeSystemContext.ArrayOfTEnumeratorType)
{
// Generic array enumerators use special variance rules recognized by the runtime
details = new GenericVarianceDetails(new[] { GenericVariance.ArrayCovariant });
}
else if (factory.TypeSystemContext.IsGenericArrayInterfaceType(_type))
{
// Runtime casting logic relies on all interface types implemented on arrays
// to have the variant flag set (even if all the arguments are non-variant).
// This supports e.g. casting uint[] to ICollection<int>
details = new GenericVarianceDetails(_type);
}
else if (_type.HasVariance)
{
details = new GenericVarianceDetails(_type);
}
else
{
details = default;
}
if (!details.IsNull)
{
ISymbolNode varianceInfoNode = factory.GenericVariance(details);
if (factory.Target.SupportsRelativePointers)
objData.EmitReloc(varianceInfoNode, RelocType.IMAGE_REL_BASED_RELPTR32);
else
objData.EmitPointerReloc(varianceInfoNode);
}
}
}
}
private void OutputFunctionPointerParameters(NodeFactory factory, ref ObjectDataBuilder objData)
{
if (_type.IsFunctionPointer)
{
MethodSignature sig = ((FunctionPointerType)_type).Signature;
foreach (TypeDesc paramType in sig)
{
ISymbolNode paramTypeNode = factory.NecessaryTypeSymbol(paramType);
if (factory.Target.SupportsRelativePointers)
objData.EmitReloc(paramTypeNode, RelocType.IMAGE_REL_BASED_RELPTR32);
else
objData.EmitPointerReloc(paramTypeNode);
}
}
}
private void OutputDispatchMap(NodeFactory factory, ref ObjectDataBuilder objData)
{
if (MightHaveInterfaceDispatchMap(factory))
{
ISymbolNode dispatchMap = factory.InterfaceDispatchMap(_type.ConvertToCanonForm(CanonicalFormKind.Specific));
if (factory.Target.SupportsRelativePointers)
objData.EmitReloc(dispatchMap, RelocType.IMAGE_REL_BASED_RELPTR32);
else
objData.EmitPointerReloc(dispatchMap);
}
}
protected override void OnMarked(NodeFactory context)
{
Debug.Assert(_type.IsTypeDefinition || !_type.HasSameTypeDefinition(context.ArrayOfTClass), "Asking for Array<T> MethodTable");
}
public override int ClassCode => 1521789141;
public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
{
return comparer.Compare(_type, ((EETypeNode)other)._type);
}
public override string ToString()
{
return _type.ToString();
}
private struct SlotCounter
{
private int _startBytes;
public static SlotCounter BeginCounting(ref /* readonly */ ObjectDataBuilder builder)
=> new SlotCounter { _startBytes = builder.CountBytes };
public int CountSlots(ref /* readonly */ ObjectDataBuilder builder)
{
int bytesEmitted = builder.CountBytes - _startBytes;
Debug.Assert(bytesEmitted % builder.TargetPointerSize == 0);
return bytesEmitted / builder.TargetPointerSize;
}
}
private sealed class WritableDataNode : ObjectNode, ISymbolDefinitionNode
{
private readonly EETypeNode _type;
public WritableDataNode(EETypeNode type) => _type = type;
public override ObjectNodeSection GetSection(NodeFactory factory)
=> _type.GetFrozenRuntimeTypeNode(factory).Marked
? (factory.Target.IsWindows ? ObjectNodeSection.ReadOnlyDataSection : ObjectNodeSection.DataSection)
: ObjectNodeSection.BssSection;
public override bool StaticDependenciesAreComputed => true;
public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
=> sb.Append("__writableData"u8).Append(nameMangler.GetMangledTypeName(_type.Type));
public int Offset => 0;
public override bool IsShareable => true;
public override bool ShouldSkipEmittingObjectNode(NodeFactory factory) => _type.ShouldSkipEmittingObjectNode(factory);
public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
{
ObjectDataBuilder builder = new ObjectDataBuilder(factory, relocsOnly);
builder.RequireInitialAlignment(WritableData.GetAlignment(factory.Target.PointerSize));
builder.AddSymbol(this);
// If the whole program view contains a reference to a preallocated RuntimeType
// instance for this type, generate a reference to it.
// Otherwise, generate as zero to save size.
if (!relocsOnly
&& !_type.Type.IsCanonicalSubtype(CanonicalFormKind.Any)
&& _type.GetFrozenRuntimeTypeNode(factory) is { Marked: true } runtimeTypeObject)
{
builder.EmitPointerReloc(runtimeTypeObject);
}
else
{
builder.EmitZeroPointer();
}
Debug.Assert(builder.CountBytes == WritableData.GetSize(factory.Target.PointerSize));
return builder.ToObjectData();
}
protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);
public override int ClassCode => -5647893;
public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
=> comparer.Compare(_type, ((WritableDataNode)other)._type);
}
}
}
|