Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow RyuJIT to refer to frozen RuntimeType instances #94342

Merged
merged 2 commits into from
Nov 6, 2023
Merged
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 @@ -405,12 +405,6 @@ internal RuntimeImports.RhCorElementTypeInfo CorElementTypeInfo
}
}

internal ref T GetWritableData<T>() where T : unmanaged
{
Debug.Assert(Internal.Runtime.WritableData.GetSize(IntPtr.Size) == sizeof(T));
return ref Unsafe.AsRef<T>((void*)_value->WritableData);
}

[Intrinsic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static EETypePtr EETypePtrOf<T>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
using Internal.Reflection.Core.Execution;
using Internal.Runtime;

using Debug = System.Diagnostics.Debug;

namespace System
{
internal sealed unsafe class RuntimeType : TypeInfo, ICloneable
Expand All @@ -33,6 +35,12 @@ internal RuntimeType(RuntimeTypeInfo runtimeTypeInfo)
_runtimeTypeInfoHandle = RuntimeImports.RhHandleAlloc(runtimeTypeInfo, GCHandleType.Normal);
}

internal void DangerousSetUnderlyingEEType(MethodTable* pEEType)
{
Debug.Assert(_pUnderlyingEEType == null);
_pUnderlyingEEType = pEEType;
}

internal void Free()
{
RuntimeImports.RhHandleFree(_runtimeTypeInfoHandle);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
Expand All @@ -30,15 +31,8 @@ internal static unsafe RuntimeType GetTypeFromMethodTable(MethodTable* pMT)
// unifier's hash table.
if (MethodTable.SupportsWritableData)
{
ref UnsafeGCHandle handle = ref Unsafe.AsRef<UnsafeGCHandle>(pMT->WritableData);
if (handle.IsAllocated)
{
return Unsafe.As<RuntimeType>(handle.Target);
}
else
{
return GetTypeFromMethodTableSlow(pMT, ref handle);
}
ref RuntimeType? type = ref Unsafe.AsRef<RuntimeType?>(pMT->WritableData);
return type ?? GetTypeFromMethodTableSlow(pMT);
}
else
{
Expand All @@ -47,17 +41,30 @@ internal static unsafe RuntimeType GetTypeFromMethodTable(MethodTable* pMT)
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static unsafe RuntimeType GetTypeFromMethodTableSlow(MethodTable* pMT, ref UnsafeGCHandle handle)
private static unsafe RuntimeType GetTypeFromMethodTableSlow(MethodTable* pMT)
{
UnsafeGCHandle tempHandle = UnsafeGCHandle.Alloc(new RuntimeType(pMT));
// TODO: instead of fragmenting the frozen object heap, we should have our own allocator
Copy link
Member

Choose a reason for hiding this comment

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

I have added check-box for this to #91704

// for objects that live forever outside the GC heap.

RuntimeType? type = null;
RuntimeImports.RhAllocateNewObject(
(IntPtr)MethodTable.Of<RuntimeType>(),
(uint)GC_ALLOC_FLAGS.GC_ALLOC_PINNED_OBJECT_HEAP,
Unsafe.AsPointer(ref type));

if (type == null)
throw new OutOfMemoryException();

type.DangerousSetUnderlyingEEType(pMT);

// We don't want to leak a handle if there's a race
if (Interlocked.CompareExchange(ref Unsafe.As<UnsafeGCHandle, IntPtr>(ref handle), Unsafe.As<UnsafeGCHandle, IntPtr>(ref tempHandle), default) != default)
ref RuntimeType? runtimeTypeCache = ref Unsafe.AsRef<RuntimeType?>(pMT->WritableData);
if (Interlocked.CompareExchange(ref runtimeTypeCache, type, null) == null)
{
tempHandle.Free();
// Create and leak a GC handle
UnsafeGCHandle.Alloc(type);
}

return Unsafe.As<RuntimeType>(handle.Target);
return runtimeTypeCache;
}

//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ protected override ObjectData GetDehydratableData(NodeFactory factory, bool relo

var builder = new ObjectDataBuilder(factory, relocsOnly);
builder.AddSymbol(this);
foreach (EmbeddedObjectNode node in factory.MetadataManager.GetFrozenObjects())
foreach (FrozenObjectNode node in factory.MetadataManager.GetFrozenObjects())
{
AlignNextObject(ref builder, factory);

Expand All @@ -46,10 +46,7 @@ protected override ObjectData GetDehydratableData(NodeFactory factory, bool relo
builder.EmitZeros(minimumObjectSize - objectSize);
}

if (node is ISymbolDefinitionNode)
{
builder.AddSymbol((ISymbolDefinitionNode)node);
}
builder.AddSymbol(node);
}

// Terminate with a null pointer as expected by the GC
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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.Diagnostics;

using Internal.TypeSystem;
Expand Down Expand Up @@ -63,6 +64,8 @@ protected override ISymbolNode GetBaseTypeNode(NodeFactory factory)
return _type.BaseType != null ? factory.NecessaryTypeSymbol(_type.BaseType.NormalizeInstantiation()) : null;
}

protected override FrozenRuntimeTypeNode GetFrozenRuntimeTypeNode(NodeFactory factory) => throw new NotSupportedException();

protected override ISymbolNode GetNonNullableValueTypeArrayElementTypeNode(NodeFactory factory)
{
return factory.ConstructedTypeSymbol(((ArrayType)_type).ElementType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ protected override ISymbolNode GetBaseTypeNode(NodeFactory factory)
return _type.BaseType != null ? factory.ConstructedTypeSymbol(_type.BaseType) : null;
}

protected override FrozenRuntimeTypeNode GetFrozenRuntimeTypeNode(NodeFactory factory)
{
return factory.SerializedConstructedRuntimeTypeObject(_type);
}

protected override ISymbolNode GetNonNullableValueTypeArrayElementTypeNode(NodeFactory factory)
{
return factory.ConstructedTypeSymbol(((ArrayType)_type).ElementType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public EETypeNode(NodeFactory factory, TypeDesc type)
Debug.Assert(!type.IsRuntimeDeterminedSubtype);
_type = type;
_optionalFieldsNode = new EETypeOptionalFieldsNode(this);
_writableDataNode = factory.Target.SupportsRelativePointers ? new WritableDataNode(this) : null;
_writableDataNode = SupportsWritableData(factory.Target) && !_type.IsCanonicalSubtype(CanonicalFormKind.Any) ? new WritableDataNode(this) : null;
_hasConditionalDependenciesFromMetadataManager = factory.MetadataManager.HasConditionalDependenciesDueToEETypePresence(type);

if (EmitVirtualSlotsAndInterfaces)
Expand All @@ -103,6 +103,12 @@ public EETypeNode(NodeFactory factory, TypeDesc type)
factory.TypeSystemContext.EnsureLoadableType(type);
}

public static bool SupportsWritableData(TargetDetails target)
=> target.SupportsRelativePointers;

public static bool SupportsFrozenRuntimeTypeInstances(TargetDetails target)
=> SupportsWritableData(target);

private static VirtualMethodAnalysisFlags AnalyzeVirtualMethods(TypeDesc type)
{
var result = VirtualMethodAnalysisFlags.None;
Expand Down Expand Up @@ -878,6 +884,11 @@ protected virtual ISymbolNode GetBaseTypeNode(NodeFactory factory)
return _type.BaseType != null ? factory.NecessaryTypeSymbol(_type.BaseType) : null;
}

protected virtual FrozenRuntimeTypeNode GetFrozenRuntimeTypeNode(NodeFactory factory)
{
return factory.SerializedNecessaryRuntimeTypeObject(_type);
}

protected virtual ISymbolNode GetNonNullableValueTypeArrayElementTypeNode(NodeFactory factory)
{
return factory.NecessaryTypeSymbol(((ArrayType)_type).ElementType);
Expand Down Expand Up @@ -1116,6 +1127,10 @@ protected void OutputWritableData(NodeFactory factory, ref ObjectDataBuilder obj
{
objData.EmitReloc(_writableDataNode, RelocType.IMAGE_REL_BASED_RELPTR32);
}
else if (SupportsWritableData(factory.Target))
{
objData.EmitInt(0);
}
}

protected void OutputOptionalFields(NodeFactory factory, ref ObjectDataBuilder objData)
Expand Down Expand Up @@ -1407,7 +1422,11 @@ private sealed class WritableDataNode : ObjectNode, ISymbolDefinitionNode
private readonly EETypeNode _type;

public WritableDataNode(EETypeNode type) => _type = type;
public override ObjectNodeSection GetSection(NodeFactory factory) => ObjectNodeSection.BssSection;
public override ObjectNodeSection GetSection(NodeFactory factory)
=> SupportsFrozenRuntimeTypeInstances(factory.Target) && _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").Append(nameMangler.GetMangledTypeName(_type.Type));
Expand All @@ -1416,10 +1435,29 @@ public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
public override bool ShouldSkipEmittingObjectNode(NodeFactory factory) => _type.ShouldSkipEmittingObjectNode(factory);

public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
=> new ObjectData(new byte[WritableData.GetSize(factory.Target.PointerSize)],
Array.Empty<Relocation>(),
WritableData.GetAlignment(factory.Target.PointerSize),
new ISymbolDefinitionNode[] { this });
{
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 (SupportsFrozenRuntimeTypeInstances(factory.Target)
&& _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);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Internal.IL;
using Internal.Text;
using Internal.TypeSystem;

using Debug = System.Diagnostics.Debug;

namespace ILCompiler.DependencyAnalysis
{
public sealed class FrozenRuntimeTypeNode : FrozenObjectNode
{
private readonly TypeDesc _type;
private readonly bool _constructed;

public FrozenRuntimeTypeNode(TypeDesc type, bool constructed)
{
Debug.Assert(EETypeNode.SupportsFrozenRuntimeTypeInstances(type.Context.Target));
_type = type;
_constructed = constructed;
}

public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
{
sb.Append(nameMangler.CompilationUnitPrefix).Append("__RuntimeType_").Append(nameMangler.GetMangledTypeName(_type));
}

protected override int ContentSize => ObjectType.InstanceByteCount.AsInt;

public override void EncodeContents(ref ObjectDataBuilder dataBuilder, NodeFactory factory, bool relocsOnly)
{
IEETypeNode typeSymbol = _constructed
? factory.ConstructedTypeSymbol(_type)
: factory.NecessaryTypeSymbol(_type);

dataBuilder.EmitPointerReloc(factory.ConstructedTypeSymbol(ObjectType));
dataBuilder.EmitPointerReloc(typeSymbol); // RuntimeType::_pUnderlyingEEType
dataBuilder.EmitZeroPointer(); // RuntimeType::_runtimeTypeInfoHandle
}

protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);

public override int ClassCode => 726422757;

public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
{
return comparer.Compare(_type, ((FrozenRuntimeTypeNode)other)._type);
}

public override int? ArrayLength => null;

public override bool IsKnownImmutable => false;

public override DefType ObjectType => _type.Context.SystemModule.GetKnownType("System", "RuntimeType");
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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.Diagnostics;

using Internal.TypeSystem;
Expand All @@ -25,6 +26,8 @@ protected override ISymbolNode GetBaseTypeNode(NodeFactory factory)
return _type.BaseType != null ? factory.NecessaryTypeSymbol(_type.BaseType.NormalizeInstantiation()) : null;
}

protected override FrozenRuntimeTypeNode GetFrozenRuntimeTypeNode(NodeFactory factory) => throw new NotSupportedException();
jkotas marked this conversation as resolved.
Show resolved Hide resolved

public override int ClassCode => 1505000724;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,16 @@ private void CreateNodeCaches()
return new SerializedFrozenObjectNode(key.OwnerType, key.AllocationSiteId, key.SerializableObject);
});

_frozenConstructedRuntimeTypeNodes = new NodeCache<TypeDesc, FrozenRuntimeTypeNode>(key =>
{
return new FrozenRuntimeTypeNode(key, constructed: true);
});

_frozenNecessaryRuntimeTypeNodes = new NodeCache<TypeDesc, FrozenRuntimeTypeNode>(key =>
{
return new FrozenRuntimeTypeNode(key, constructed: false);
});

_interfaceDispatchCells = new NodeCache<DispatchCellKey, InterfaceDispatchCellNode>(callSiteCell =>
{
return new InterfaceDispatchCellNode(callSiteCell.Target, callSiteCell.CallsiteId);
Expand Down Expand Up @@ -1227,6 +1237,20 @@ public SerializedFrozenObjectNode SerializedFrozenObject(MetadataType owningType
return _frozenObjectNodes.GetOrAdd(new SerializedFrozenObjectKey(owningType, allocationSiteId, data));
}

private NodeCache<TypeDesc, FrozenRuntimeTypeNode> _frozenConstructedRuntimeTypeNodes;

public FrozenRuntimeTypeNode SerializedConstructedRuntimeTypeObject(TypeDesc type)
{
return _frozenConstructedRuntimeTypeNodes.GetOrAdd(type);
}

private NodeCache<TypeDesc, FrozenRuntimeTypeNode> _frozenNecessaryRuntimeTypeNodes;

public FrozenRuntimeTypeNode SerializedNecessaryRuntimeTypeObject(TypeDesc type)
{
return _frozenNecessaryRuntimeTypeNodes.GetOrAdd(type);
}

private NodeCache<MethodDesc, EmbeddedObjectNode> _eagerCctorIndirectionNodes;

public EmbeddedObjectNode EagerCctorIndirection(MethodDesc cctorMethod)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@
<Compile Include="Compiler\DependencyAnalysis\ExternSymbolsImportedNodeProvider.cs" />
<Compile Include="Compiler\DependencyAnalysis\FieldRvaDataNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\FrozenObjectNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\FrozenRuntimeTypeNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\FunctionPointerMapNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\GenericMethodsHashtableEntryNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\GenericStaticBaseInfoNode.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ public override IEETypeNode NecessaryTypeSymbolIfPossible(TypeDesc type)
return _nodeFactory.NecessaryTypeSymbol(type);
}

public FrozenRuntimeTypeNode NecessaryRuntimeTypeIfPossible(TypeDesc type)
{
bool canPotentiallyConstruct = _devirtualizationManager == null
? true : _devirtualizationManager.CanConstructType(type);
if (canPotentiallyConstruct && ConstructedEETypeNode.CreationAllowed(type))
return _nodeFactory.SerializedConstructedRuntimeTypeObject(type);

return _nodeFactory.SerializedNecessaryRuntimeTypeObject(type);
}

protected override void CompileInternal(string outputFile, ObjectDumper dumper)
{
_dependencyGraph.ComputeMarkedNodes();
Expand Down
Loading
Loading