Skip to content

Commit

Permalink
Allow preinitializing RuntimeType instances (#94405)
Browse files Browse the repository at this point in the history
Unblock serializing `RuntimeType` instances from the static constructor interpreter.

With this we can now preinitialize `static readonly Type s_foo = typeof(Foo)` and RyuJIT will optimize reads of `s_foo` into loading a constant value.

Contributes to #91704.
  • Loading branch information
MichalStrehovsky committed Nov 13, 2023
1 parent 82a8579 commit 1347af7
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 15 deletions.
2 changes: 1 addition & 1 deletion src/coreclr/jit/importer.cpp
Expand Up @@ -8989,6 +8989,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
FALLTHROUGH;
case CORINFO_FIELD_STATIC_SHARED_STATIC_HELPER:
case CORINFO_FIELD_STATIC_ADDRESS:
case CORINFO_FIELD_STATIC_RELOCATABLE:
// Replace static read-only fields with constant if possible
if ((aflags & CORINFO_ACCESS_GET) && (fieldInfo.fieldFlags & CORINFO_FLG_FIELD_FINAL))
{
Expand All @@ -9005,7 +9006,6 @@ void Compiler::impImportBlockCode(BasicBlock* block)
case CORINFO_FIELD_STATIC_RVA_ADDRESS:
case CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER:
case CORINFO_FIELD_STATIC_READYTORUN_HELPER:
case CORINFO_FIELD_STATIC_RELOCATABLE:
op1 = impImportStaticFieldAddress(&resolvedToken, (CORINFO_ACCESS_FLAGS)aflags, &fieldInfo,
lclTyp, &indirFlags);
break;
Expand Down
Expand Up @@ -1237,6 +1237,13 @@ public SerializedFrozenObjectNode SerializedFrozenObject(MetadataType owningType
return _frozenObjectNodes.GetOrAdd(new SerializedFrozenObjectKey(owningType, allocationSiteId, data));
}

public FrozenRuntimeTypeNode SerializedMaximallyConstructableRuntimeTypeObject(TypeDesc type)
{
if (ConstructedEETypeNode.CreationAllowed(type))
return SerializedConstructedRuntimeTypeObject(type);
return SerializedNecessaryRuntimeTypeObject(type);
}

private NodeCache<TypeDesc, FrozenRuntimeTypeNode> _frozenConstructedRuntimeTypeNodes;

public FrozenRuntimeTypeNode SerializedConstructedRuntimeTypeObject(TypeDesc type)
Expand Down
38 changes: 28 additions & 10 deletions src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs
Expand Up @@ -375,7 +375,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
if (value is ValueTypeValue)
stack.PushFromLocation(field.FieldType, value);
else if (value is ReferenceTypeValue referenceType)
stack.PushFromLocation(field.FieldType, referenceType.ToForeignInstance(baseInstructionCounter));
stack.PushFromLocation(field.FieldType, referenceType.ToForeignInstance(baseInstructionCounter, this));
else
return Status.Fail(methodIL.OwningMethod, opcode);
}
Expand Down Expand Up @@ -2386,7 +2386,7 @@ public override bool GetRawData(NodeFactory factory, out object data)
}
}

private sealed class RuntimeTypeValue : ReferenceTypeValue, IInternalModelingOnlyValue
private sealed class RuntimeTypeValue : ReferenceTypeValue
{
public TypeDesc TypeRepresented { get; }

Expand All @@ -2398,11 +2398,21 @@ public RuntimeTypeValue(TypeDesc type)

public override bool GetRawData(NodeFactory factory, out object data)
{
data = null;
return false;
data = factory.SerializedMaximallyConstructableRuntimeTypeObject(TypeRepresented);
return true;
}
public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter, TypePreinit preinitContext)
{
if (!preinitContext._internedTypes.TryGetValue(TypeRepresented, out RuntimeTypeValue result))
{
preinitContext._internedTypes.Add(TypeRepresented, result = new RuntimeTypeValue(TypeRepresented));
}
return result;
}
public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
{
builder.EmitPointerReloc(factory.SerializedMaximallyConstructableRuntimeTypeObject(TypeRepresented));
}
public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter) => this;
public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory) => throw new NotImplementedException();
}

private sealed class ReadOnlySpanValue : BaseValueTypeValue, IInternalModelingOnlyValue
Expand Down Expand Up @@ -2639,7 +2649,7 @@ public override bool Equals(Value value)
return this == value;
}

public abstract ReferenceTypeValue ToForeignInstance(int baseInstructionCounter);
public abstract ReferenceTypeValue ToForeignInstance(int baseInstructionCounter, TypePreinit preinitContext);
}

private struct AllocationSite
Expand Down Expand Up @@ -2667,7 +2677,7 @@ public AllocatedReferenceTypeValue(TypeDesc type, AllocationSite allocationSite)
AllocationSite = allocationSite;
}

public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter) =>
public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter, TypePreinit preinitContext) =>
new ForeignTypeInstance(
Type,
new AllocationSite(AllocationSite.OwningType, AllocationSite.InstructionCounter - baseInstructionCounter),
Expand Down Expand Up @@ -2887,7 +2897,7 @@ public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory f
}
}

public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter) => this;
public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter, TypePreinit preinitContext) => this;
}

private sealed class StringInstance : ReferenceTypeValue, IHasInstanceFields
Expand Down Expand Up @@ -2946,7 +2956,15 @@ public override bool GetRawData(NodeFactory factory, out object data)
return true;
}

public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter) => this;
public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter, TypePreinit preinitContext)
{
string value = ValueAsString;
if (!preinitContext._internedStrings.TryGetValue(value, out StringInstance result))
{
preinitContext._internedStrings.Add(value, result = new StringInstance(Type, value));
}
return result;
}
Value IHasInstanceFields.GetField(FieldDesc field) => new FieldAccessor(_value).GetField(field);
bool IHasInstanceFields.TrySetField(FieldDesc field, Value value) => false;
ByRefValue IHasInstanceFields.GetFieldAddress(FieldDesc field) => new FieldAccessor(_value).GetFieldAddress(field);
Expand Down
Expand Up @@ -83,8 +83,8 @@ public FrozenRuntimeTypeNode NecessaryRuntimeTypeIfPossible(TypeDesc type)
{
bool canPotentiallyConstruct = _devirtualizationManager == null
? true : _devirtualizationManager.CanConstructType(type);
if (canPotentiallyConstruct && ConstructedEETypeNode.CreationAllowed(type))
return _nodeFactory.SerializedConstructedRuntimeTypeObject(type);
if (canPotentiallyConstruct)
return _nodeFactory.SerializedMaximallyConstructableRuntimeTypeObject(type);

return _nodeFactory.SerializedNecessaryRuntimeTypeObject(type);
}
Expand Down
Expand Up @@ -657,13 +657,15 @@ class OtherClass
static int s_intValue = OtherClass.IntValue;
static string s_stringValue = OtherClass.StringValue;
static object s_objectValue = OtherClass.ObjectValue;
static bool s_areStringsSame = Object.ReferenceEquals(OtherClass.StringValue, "Hello");

public static void Run()
{
Assert.IsPreinitialized(typeof(TestInitFromOtherClass));
Assert.AreEqual(OtherClass.IntValue, s_intValue);
Assert.AreSame(OtherClass.StringValue, s_stringValue);
Assert.AreSame(OtherClass.ObjectValue, s_objectValue);
Assert.True(s_areStringsSame);
}
}

Expand Down Expand Up @@ -1282,8 +1284,8 @@ public static void Run()
Assert.True(!Foo<bool>.IsChar);
Assert.True(Foo<bool>.IsBool);

Assert.IsLazyInitialized(typeof(CharHolder));
Assert.IsLazyInitialized(typeof(IsChar));
Assert.IsPreinitialized(typeof(CharHolder));
Assert.IsPreinitialized(typeof(IsChar));
Assert.True(IsChar.Is);
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs
Expand Up @@ -689,6 +689,8 @@ class C3<T> : IInterface, IInterface<T>
static IInterface<object> s_c3a = new C3<object>();
static IInterface s_c3b = new C3<object>();

// Works around https://github.com/dotnet/runtime/issues/94399
[MethodImpl(MethodImplOptions.NoOptimization)]
public static void Run()
{
if (s_c1.Method(null) != "Method(object)")
Expand Down

0 comments on commit 1347af7

Please sign in to comment.