Skip to content

Commit

Permalink
Allow RyuJIT to refer to frozen RuntimeType instances
Browse files Browse the repository at this point in the history
With dotnet#93440 it became possible to place `RuntimeType` instances in the frozen region. This adds support for generating frozen `RuntimeType` instances within the compiler. We give these to RyuJIT whenever it asks for them.

* New `FrozenObjectNode` descendant that represents a `RuntimeType` instance. We have two flavors - one wraps constructed and the other wraps necessary `MethodTable`s (we have a need for freezing both kinds to keep our ability to optimize `RuntimeTypes` used in comparisons only).
* We hand out references to these whenever RyuJIT needs them.
* At `MethodTable` emission time, we check whether a frozen `RuntimeType` for this `MethodTable` was generated. If so, we pre-populate `MethodTable`’s `WritableData` section with a reloc to the `RuntimeType`. Otherwise we use null as usual.
* At runtime for `GetTypeFromEEType`, if `WritableData` is non-null, we just return that. Otherwise we create a pinned `RuntimeType` instance and write it into `WritableData`. In the future, we should allocate this on a frozen heap.

Old codegen for `Console.WriteLine(typeof(Program))`:

```asm
sub         rsp,28h
lea         rcx,[repro_Program::`vftable' (07FF7D03154E0h)]
call        S_P_CoreLib_Internal_Runtime_CompilerHelpers_LdTokenHelpers__GetRuntimeType (07FF7D0253290h)
mov         rcx,rax
call        System_Console_System_Console__WriteLine_11 (07FF7D0262AC0h)
nop
add         rsp,28h
ret
```

New codegen:

```asm
sub         rsp,28h
lea         rcx,[__RuntimeType_repro_Program (07FF7A218EC50h)]
call        System_Console_System_Console__WriteLine_11 (07FF7A20F2680h)
nop
add         rsp,28h
ret
```

I’ll do cctor preinitialization in a subsequent PR to keep things short.
Contributes to dotnet#91704.
  • Loading branch information
MichalStrehovsky committed Nov 3, 2023
1 parent 655b177 commit d568bba
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 34 deletions.
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, ref type);
}
else
{
Expand All @@ -47,17 +41,29 @@ 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, ref RuntimeType? runtimeTypeCache)
{
UnsafeGCHandle tempHandle = UnsafeGCHandle.Alloc(new RuntimeType(pMT));
// TODO: instead of fragmenting the frozen object heap, we should have our own allocator
// 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)
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();

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

0 comments on commit d568bba

Please sign in to comment.