Skip to content

Commit 9060368

Browse files
Intrinsify CreateSpan in static constructor interpreter (#81747)
Introduction of `CreateSpan` and its use in CoreLib regressed the number of types we can preinitialize. To add insult to injury, we were also hitting throwing paths in the cctor interpreter because the patterns generated by the C# compiler got lumped into uninteresting IL-only sequences we treated as invalid IL (the cctor interpreter should really only throw for invalid IL, but sometimes we throw for valid-but-uncommon IL too). As a result, even a hello world was hitting half a dozen first chance exceptions. This adds intrinsic treatment to `CreateSpan`. We also need to treat `ReadOnlySpan` specially because of #78681. That issue is basically a rewrite of the interpreter memory model and likely won't be addressed anytime soon.
1 parent 2f2e9fe commit 9060368

File tree

2 files changed

+241
-3
lines changed

2 files changed

+241
-3
lines changed

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs

Lines changed: 186 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -895,11 +895,11 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
895895
}
896896

897897
TypeDesc token = (TypeDesc)methodIL.GetObject(reader.ReadILToken());
898-
if (token.IsGCPointer)
898+
if (token.IsGCPointer || popped.Value is not ByRefValue byrefVal)
899899
{
900900
return Status.Fail(methodIL.OwningMethod, opcode);
901901
}
902-
((ByRefValue)popped.Value).Initialize(token.GetElementSize().AsInt);
902+
byrefVal.Initialize(token.GetElementSize().AsInt);
903903
}
904904
break;
905905

@@ -1415,6 +1415,53 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
14151415
}
14161416
break;
14171417

1418+
case ILOpcode.ldind_i1:
1419+
case ILOpcode.ldind_u1:
1420+
case ILOpcode.ldind_i2:
1421+
case ILOpcode.ldind_u2:
1422+
case ILOpcode.ldind_i4:
1423+
case ILOpcode.ldind_u4:
1424+
case ILOpcode.ldind_i8:
1425+
{
1426+
StackEntry entry = stack.Pop();
1427+
if (entry.Value is ByRefValue byRefVal)
1428+
{
1429+
switch (opcode)
1430+
{
1431+
case ILOpcode.ldind_i1:
1432+
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(byRefVal.DereferenceAsSByte()));
1433+
break;
1434+
case ILOpcode.ldind_u1:
1435+
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((byte)byRefVal.DereferenceAsSByte()));
1436+
break;
1437+
case ILOpcode.ldind_i2:
1438+
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(byRefVal.DereferenceAsInt16()));
1439+
break;
1440+
case ILOpcode.ldind_u2:
1441+
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((ushort)byRefVal.DereferenceAsInt16()));
1442+
break;
1443+
case ILOpcode.ldind_i4:
1444+
case ILOpcode.ldind_u4:
1445+
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(byRefVal.DereferenceAsInt32()));
1446+
break;
1447+
case ILOpcode.ldind_i8:
1448+
stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64(byRefVal.DereferenceAsInt64()));
1449+
break;
1450+
case ILOpcode.ldind_r4:
1451+
stack.Push(StackValueKind.Float, ValueTypeValue.FromDouble(byRefVal.DereferenceAsSingle()));
1452+
break;
1453+
case ILOpcode.ldind_r8:
1454+
stack.Push(StackValueKind.Float, ValueTypeValue.FromDouble(byRefVal.DereferenceAsDouble()));
1455+
break;
1456+
}
1457+
}
1458+
else
1459+
{
1460+
ThrowHelper.ThrowInvalidProgramException();
1461+
}
1462+
}
1463+
break;
1464+
14181465
case ILOpcode.constrained:
14191466
// Fallthrough. If this is ever implemented, make sure delegates to static virtual methods
14201467
// are also handled. We currently assume the frozen delegate will not be to a static
@@ -1428,12 +1475,20 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
14281475
return Status.Fail(methodIL.OwningMethod, "Control fell through");
14291476
}
14301477

1431-
private static ValueTypeValue NewUninitializedLocationValue(TypeDesc locationType)
1478+
private static BaseValueTypeValue NewUninitializedLocationValue(TypeDesc locationType)
14321479
{
14331480
if (locationType.IsGCPointer || locationType.IsByRef)
14341481
{
14351482
return null;
14361483
}
1484+
else if (locationType.IsByRefLike && locationType is MetadataType maybeReadOnlySpan
1485+
&& maybeReadOnlySpan.Module == locationType.Context.SystemModule
1486+
&& maybeReadOnlySpan.Name == "ReadOnlySpan`1"
1487+
&& maybeReadOnlySpan.Namespace == "System"
1488+
&& maybeReadOnlySpan.Instantiation[0] is MetadataType readOnlySpanElementType)
1489+
{
1490+
return new ReadOnlySpanValue(readOnlySpanElementType, Array.Empty<byte>());
1491+
}
14371492
else
14381493
{
14391494
Debug.Assert(locationType.IsValueType || locationType.IsPointer || locationType.IsFunctionPointer);
@@ -1460,6 +1515,33 @@ private static bool TryHandleIntrinsicCall(MethodDesc method, Value[] parameters
14601515
return array.TryInitialize(rvaData);
14611516
}
14621517
return false;
1518+
case "CreateSpan":
1519+
if (method.OwningType is MetadataType createSpanType
1520+
&& createSpanType.Name == "RuntimeHelpers" && createSpanType.Namespace == "System.Runtime.CompilerServices"
1521+
&& createSpanType.Module == createSpanType.Context.SystemModule
1522+
&& parameters[0] is RuntimeFieldHandleValue createSpanFieldHandle
1523+
&& createSpanFieldHandle.Field.IsStatic && createSpanFieldHandle.Field.HasRva
1524+
&& createSpanFieldHandle.Field is Internal.TypeSystem.Ecma.EcmaField createSpanEcmaField
1525+
&& method.Instantiation[0].IsValueType)
1526+
{
1527+
var elementType = (MetadataType)method.Instantiation[0];
1528+
int elementSize = elementType.InstanceFieldSize.AsInt;
1529+
byte[] rvaData = Internal.TypeSystem.Ecma.EcmaFieldExtensions.GetFieldRvaData(createSpanEcmaField);
1530+
if (rvaData.Length % elementSize != 0)
1531+
return false;
1532+
retVal = new ReadOnlySpanValue(elementType, rvaData);
1533+
return true;
1534+
}
1535+
return false;
1536+
case "get_Item":
1537+
if (method.OwningType is MetadataType readonlySpanType
1538+
&& readonlySpanType.Name == "ReadOnlySpan`1" && readonlySpanType.Namespace == "System"
1539+
&& parameters[0] is ReadOnlySpanReferenceValue spanRef
1540+
&& parameters[1] is ValueTypeValue spanIndex)
1541+
{
1542+
return spanRef.TryAccessElement(spanIndex.AsInt32(), out retVal);
1543+
}
1544+
return false;
14631545
}
14641546

14651547
return false;
@@ -1938,6 +2020,93 @@ public override bool GetRawData(NodeFactory factory, out object data)
19382020
}
19392021
}
19402022

2023+
private sealed class ReadOnlySpanValue : BaseValueTypeValue, IInternalModelingOnlyValue
2024+
{
2025+
private readonly MetadataType _elementType;
2026+
private readonly byte[] _bytes;
2027+
2028+
public ReadOnlySpanValue(MetadataType elementType, byte[] bytes)
2029+
{
2030+
_elementType = elementType;
2031+
_bytes = bytes;
2032+
}
2033+
2034+
public override int Size => 2 * _elementType.Context.Target.PointerSize;
2035+
2036+
public override bool Equals(Value value)
2037+
{
2038+
// ceq instruction on ReadOnlySpans is hard to support.
2039+
// We should not see it in the first place.
2040+
ThrowHelper.ThrowInvalidProgramException();
2041+
return false;
2042+
}
2043+
2044+
public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
2045+
{
2046+
throw new NotSupportedException();
2047+
}
2048+
2049+
public override bool GetRawData(NodeFactory factory, out object data)
2050+
{
2051+
data = null;
2052+
return false;
2053+
}
2054+
2055+
public override Value Clone()
2056+
{
2057+
// ReadOnlySpan is immutable and there's no way for the data to escape
2058+
return this;
2059+
}
2060+
2061+
public override bool TryCreateByRef(out Value value)
2062+
{
2063+
value = new ReadOnlySpanReferenceValue(_elementType, _bytes);
2064+
return true;
2065+
}
2066+
}
2067+
2068+
private sealed class ReadOnlySpanReferenceValue : Value
2069+
{
2070+
private readonly MetadataType _elementType;
2071+
private readonly byte[] _bytes;
2072+
2073+
public ReadOnlySpanReferenceValue(MetadataType elementType, byte[] bytes)
2074+
{
2075+
_elementType = elementType;
2076+
_bytes = bytes;
2077+
}
2078+
2079+
public override bool Equals(Value value)
2080+
{
2081+
// ceq instruction on refs to ReadOnlySpans is hard to support.
2082+
// We should not see it in the first place.
2083+
ThrowHelper.ThrowInvalidProgramException();
2084+
return false;
2085+
}
2086+
2087+
public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
2088+
{
2089+
throw new NotSupportedException();
2090+
}
2091+
2092+
public override bool GetRawData(NodeFactory factory, out object data)
2093+
{
2094+
data = null;
2095+
return false;
2096+
}
2097+
2098+
public bool TryAccessElement(int index, out Value value)
2099+
{
2100+
value = default;
2101+
int limit = _bytes.Length / _elementType.InstanceFieldSize.AsInt;
2102+
if (index >= limit)
2103+
return false;
2104+
2105+
value = new ByRefValue(_bytes, index * _elementType.InstanceFieldSize.AsInt);
2106+
return true;
2107+
}
2108+
}
2109+
19412110
private sealed class MethodPointerValue : BaseValueTypeValue, IInternalModelingOnlyValue
19422111
{
19432112
public MethodDesc PointedToMethod { get; }
@@ -2021,6 +2190,20 @@ public override bool GetRawData(NodeFactory factory, out object data)
20212190
data = null;
20222191
return false;
20232192
}
2193+
2194+
private ReadOnlySpan<byte> AsExactByteCount(int count)
2195+
{
2196+
if (PointedToOffset + count > PointedToBytes.Length)
2197+
ThrowHelper.ThrowInvalidProgramException();
2198+
return new ReadOnlySpan<byte>(PointedToBytes, PointedToOffset, count);
2199+
}
2200+
2201+
public sbyte DereferenceAsSByte() => (sbyte)AsExactByteCount(1)[0];
2202+
public short DereferenceAsInt16() => BitConverter.ToInt16(AsExactByteCount(2));
2203+
public int DereferenceAsInt32() => BitConverter.ToInt32(AsExactByteCount(4));
2204+
public long DereferenceAsInt64() => BitConverter.ToInt64(AsExactByteCount(8));
2205+
public float DereferenceAsSingle() => BitConverter.ToSingle(AsExactByteCount(4));
2206+
public double DereferenceAsDouble() => BitConverter.ToDouble(AsExactByteCount(8));
20242207
}
20252208

20262209
private abstract class ReferenceTypeValue : Value

src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ private static int Main()
4848
TestInstanceDelegate.Run();
4949
TestStringFields.Run();
5050
TestSharedCode.Run();
51+
TestReadOnlySpan.Run();
5152
#else
5253
Console.WriteLine("Preinitialization is disabled in multimodule builds for now. Skipping test.");
5354
#endif
@@ -1000,6 +1001,60 @@ public static void Run()
10001001
}
10011002
}
10021003

1004+
class TestReadOnlySpan
1005+
{
1006+
class SimpleReadOnlySpanAccess
1007+
{
1008+
private static ReadOnlySpan<int> Ints => new int[] { 5, 6, 7, 8 };
1009+
1010+
public /* not readonly on purpose */ static int Sum;
1011+
1012+
static SimpleReadOnlySpanAccess()
1013+
{
1014+
ReadOnlySpan<int> val = Ints;
1015+
Sum = val[0] + val[1] + val[2] + val[3];
1016+
}
1017+
}
1018+
1019+
class OutOfRangeAccess
1020+
{
1021+
private static ReadOnlySpan<int> Ints => new int[] { 5, 6, 7, 8 };
1022+
1023+
public readonly static int Sum;
1024+
1025+
static OutOfRangeAccess()
1026+
{
1027+
ReadOnlySpan<int> val = Ints;
1028+
Sum = val[4];
1029+
}
1030+
}
1031+
1032+
class DefaultInstanceAccess
1033+
{
1034+
public readonly static int Sum;
1035+
1036+
static DefaultInstanceAccess()
1037+
{
1038+
ReadOnlySpan<int> val = default;
1039+
Sum = val[0];
1040+
}
1041+
}
1042+
1043+
public static void Run()
1044+
{
1045+
Assert.IsPreinitialized(typeof(SimpleReadOnlySpanAccess));
1046+
Assert.AreEqual(26, SimpleReadOnlySpanAccess.Sum);
1047+
1048+
Assert.IsLazyInitialized(typeof(OutOfRangeAccess));
1049+
if (SimpleReadOnlySpanAccess.Sum == 1000) // never true
1050+
OutOfRangeAccess.Sum.ToString(); // make sure cctor is looked at
1051+
1052+
Assert.IsLazyInitialized(typeof(DefaultInstanceAccess));
1053+
if (SimpleReadOnlySpanAccess.Sum == 1000) // never true
1054+
DefaultInstanceAccess.Sum.ToString(); // make sure cctor is looked at
1055+
}
1056+
}
1057+
10031058
static class Assert
10041059
{
10051060
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern",

0 commit comments

Comments
 (0)