diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs index 7676ec1ae2c7a0..d511fea16e8021 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs @@ -10,6 +10,7 @@ using Internal.IL; using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; using CombinedDependencyList = System.Collections.Generic.List.CombinedDependencyListEntry>; using FlowAnnotations = ILLink.Shared.TrimAnalysis.FlowAnnotations; @@ -42,6 +43,7 @@ public class TypePreinit private readonly Dictionary _internedStrings = new Dictionary(); private readonly Dictionary _internedTypes = new Dictionary(); private readonly Dictionary _nestedPreinitResults = new Dictionary(); + private readonly Dictionary _rvaFieldDatas = new Dictionary(); private TypePreinit(MetadataType owningType, CompilationModuleGroup compilationGroup, ILProvider ilProvider, TypePreinitializationPolicy policy, ReadOnlyFieldPolicy readOnlyPolicy, FlowAnnotations flowAnnotations) { @@ -144,6 +146,13 @@ private bool TryGetNestedPreinitResult(MethodDesc callingMethod, MetadataType ty return true; } + private byte[] GetFieldRvaData(EcmaField field) + { + if (!_rvaFieldDatas.TryGetValue(field, out byte[] result)) + _rvaFieldDatas.Add(field, result = field.GetFieldRvaData()); + return result; + } + private Status TryScanMethod(MethodDesc method, Value[] parameters, Stack recursionProtect, ref int instructionCounter, out Value returnValue) { MethodIL methodIL = _ilProvider.GetMethodIL(method); @@ -387,7 +396,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack::.ctor(void*, int32) StackEntry entry = stack.Pop(); long size = entry.ValueKind switch { @@ -725,31 +708,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack 8192) return Status.Fail(methodIL.OwningMethod, ILOpcode.localloc); - opcode = reader.ReadILOpcode(); - if (opcode < ILOpcode.ldc_i4_0 || opcode > ILOpcode.ldc_i4) - return Status.Fail(methodIL.OwningMethod, ILOpcode.localloc); - - int maybeSpanLength = opcode switch - { - ILOpcode.ldc_i4_s => (sbyte)reader.ReadILByte(), - ILOpcode.ldc_i4 => (int)reader.ReadILUInt32(), - _ => opcode - ILOpcode.ldc_i4_0, - }; - - opcode = reader.ReadILOpcode(); - if (opcode != ILOpcode.newobj) - return Status.Fail(methodIL.OwningMethod, ILOpcode.localloc); - - var ctorMethod = (MethodDesc)methodIL.GetObject(reader.ReadILToken()); - if (!TryGetSpanElementType(ctorMethod.OwningType, isReadOnlySpan: false, out MetadataType elementType) - || ctorMethod.Signature.Length != 2 - || !ctorMethod.Signature[0].IsPointer - || !ctorMethod.Signature[1].IsWellKnownType(WellKnownType.Int32) - || maybeSpanLength * elementType.InstanceFieldSize.AsInt != size) - return Status.Fail(methodIL.OwningMethod, ILOpcode.localloc); - - var instance = new ReadOnlySpanValue(elementType, new byte[size], index: 0, (int)size); - stack.PushFromLocation(ctorMethod.OwningType, instance); + stack.Push(StackValueKind.NativeInt, new ByRefValue(new byte[size], pointedToOffset: 0)); } break; @@ -770,11 +729,6 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack(), 0, 0); + return new SpanValue(readOnlySpanElementType, Array.Empty(), 0, 0); } else if (TryGetSpanElementType(locationType, isReadOnlySpan: false, out MetadataType spanElementType)) { - return new ReadOnlySpanValue(spanElementType, Array.Empty(), 0, 0); + return new SpanValue(spanElementType, Array.Empty(), 0, 0); } else if (VTableLikeStructValue.IsCompatible(locationType)) { @@ -1954,19 +1908,10 @@ private bool TryHandleIntrinsicCall(MethodDesc method, Value[] parameters, out V byte[] rvaData = Internal.TypeSystem.Ecma.EcmaFieldExtensions.GetFieldRvaData(createSpanEcmaField); if (rvaData.Length % elementSize != 0) return false; - retVal = new ReadOnlySpanValue(elementType, rvaData, 0, rvaData.Length); + retVal = new SpanValue(elementType, rvaData, 0, rvaData.Length); return true; } return false; - case "get_Item": - if (method.OwningType is MetadataType readonlySpanType - && readonlySpanType.Name == "ReadOnlySpan`1" && readonlySpanType.Namespace == "System" - && parameters[0] is ReadOnlySpanReferenceValue spanRef - && parameters[1] is ValueTypeValue spanIndex) - { - return spanRef.TryAccessElement(spanIndex.AsInt32(), out retVal); - } - return false; case "GetTypeFromHandle" when IsSystemType(method.OwningType) && parameters[0] is RuntimeTypeHandleValue typeHandle: { @@ -1989,6 +1934,25 @@ private bool TryHandleIntrinsicCall(MethodDesc method, Value[] parameters, out V retVal = ValueTypeValue.FromSByte(parameters[0] == parameters[1] ? (sbyte)1 : (sbyte)0); return true; } + case "IsReferenceOrContainsReferences" when method.Instantiation.Length == 1 + && method.OwningType is MetadataType isReferenceOrContainsReferencesType + && isReferenceOrContainsReferencesType.Name == "RuntimeHelpers" && isReferenceOrContainsReferencesType.Namespace == "System.Runtime.CompilerServices" + && isReferenceOrContainsReferencesType.Module == method.Context.SystemModule: + { + bool result = method.Instantiation[0].IsGCPointer || (method.Instantiation[0] is DefType defType && defType.ContainsGCPointers); + retVal = ValueTypeValue.FromSByte(result ? (sbyte)1 : (sbyte)0); + return true; + } + case "GetArrayDataReference" when method.Instantiation.Length == 1 + && method.OwningType is MetadataType getArrayDataReferenceType + && getArrayDataReferenceType.Name == "MemoryMarshal" && getArrayDataReferenceType.Namespace == "System.Runtime.InteropServices" + && getArrayDataReferenceType.Module == method.Context.SystemModule + && parameters[0] is ArrayInstance arrayData + && ((ArrayType)arrayData.Type).ElementType == method.Instantiation[0]: + { + retVal = arrayData.GetArrayData(); + return true; + } } static bool IsSystemType(TypeDesc type) @@ -2212,7 +2176,8 @@ public Value PopIntoLocation(TypeDesc locationType) return ValueTypeValue.FromSingle((float)popped.Value.AsDouble()); case StackValueKind.ByRef: - if (!locationType.IsByRef) + if (!locationType.IsByRef + && locationType.Category is not TypeFlags.IntPtr and not TypeFlags.UIntPtr and not TypeFlags.Pointer and not TypeFlags.FunctionPointer) { ThrowHelper.ThrowInvalidProgramException(); } @@ -2353,7 +2318,7 @@ public ValueTypeValue(TypeDesc type) InstanceBytes = new byte[type.GetElementSize().AsInt]; } - private ValueTypeValue(byte[] bytes) + public ValueTypeValue(byte[] bytes) { InstanceBytes = bytes; } @@ -2753,14 +2718,14 @@ public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory f } } - private sealed class ReadOnlySpanValue : BaseValueTypeValue, IInternalModelingOnlyValue + private sealed class SpanValue : BaseValueTypeValue, IInternalModelingOnlyValue { private readonly MetadataType _elementType; - private readonly byte[] _bytes; - private readonly int _index; - private readonly int _length; + private byte[] _bytes; + private int _index; + private int _length; - public ReadOnlySpanValue(MetadataType elementType, byte[] bytes, int index, int length) + public SpanValue(MetadataType elementType, byte[] bytes, int index, int length) { Debug.Assert(index <= bytes.Length); Debug.Assert(length <= bytes.Length - index); @@ -2791,85 +2756,90 @@ public override bool GetRawData(NodeFactory factory, out object data) public override Value Clone() { - // ReadOnlySpan is immutable and there's no way for the data to escape - return this; + return new SpanValue(_elementType, _bytes, _index, _length); } public override bool TryCreateByRef(out Value value) { - value = new ReadOnlySpanReferenceValue(_elementType, _bytes, _index, _length); + value = new SpanReferenceValue(this); return true; } - } - - private sealed class ReadOnlySpanReferenceValue : ByRefValueBase, IHasInstanceFields - { - private readonly MetadataType _elementType; - private readonly byte[] _bytes; - private readonly int _index; - private readonly int _length; - public ReadOnlySpanReferenceValue(MetadataType elementType, byte[] bytes, int index, int length) + private sealed class SpanReferenceValue : ByRefValueBase, IHasInstanceFields { - Debug.Assert(index <= bytes.Length); - Debug.Assert(length <= bytes.Length - index); - _elementType = elementType; - _bytes = bytes; - _index = index; - _length = length; - } + private readonly SpanValue _value; - public override bool TryCompareEquality(Value value, out bool result) - { - result = false; - return false; - } + public SpanReferenceValue(SpanValue value) + { + _value = value; + } - public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory) - { - throw new NotSupportedException(); - } + public override bool TryCompareEquality(Value value, out bool result) + { + result = false; + return false; + } - public override bool GetRawData(NodeFactory factory, out object data) - { - data = null; - return false; - } + public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory) + { + throw new NotSupportedException(); + } - public bool TryAccessElement(int index, out Value value) - { - value = default; - int limit = _length / _elementType.InstanceFieldSize.AsInt; - if (index >= limit) + public override bool GetRawData(NodeFactory factory, out object data) + { + data = null; return false; + } - value = new ByRefValue(_bytes, _index + index * _elementType.InstanceFieldSize.AsInt); - return true; - } + public bool TrySetField(FieldDesc field, Value value) + { + MetadataType elementType; + if (!TryGetSpanElementType(field.OwningType, isReadOnlySpan: true, out elementType) + && !TryGetSpanElementType(field.OwningType, isReadOnlySpan: false, out elementType)) + return false; - public bool TrySetField(FieldDesc field, Value value) => false; + if (elementType != _value._elementType) + return false; - public Value GetField(FieldDesc field) - { - MetadataType elementType; - if (!TryGetSpanElementType(field.OwningType, isReadOnlySpan: true, out elementType) - && !TryGetSpanElementType(field.OwningType, isReadOnlySpan: false, out elementType)) - ThrowHelper.ThrowInvalidProgramException(); + if (field.Name == "_length") + { + _value._length = value.AsInt32() * _value._elementType.InstanceFieldSize.AsInt; + return true; + } - if (elementType != _elementType) - ThrowHelper.ThrowInvalidProgramException(); + if (value is ByRefValue byref) + { + Debug.Assert(field.Name == "_reference"); + _value._bytes = byref.PointedToBytes; + _value._index = byref.PointedToOffset; + return true; + } - if (field.Name == "_length") - return ValueTypeValue.FromInt32(_length / _elementType.InstanceFieldSize.AsInt); + return false; + } - Debug.Assert(field.Name == "_reference"); - return new ByRefValue(_bytes, _index); - } + public Value GetField(FieldDesc field) + { + MetadataType elementType; + if (!TryGetSpanElementType(field.OwningType, isReadOnlySpan: true, out elementType) + && !TryGetSpanElementType(field.OwningType, isReadOnlySpan: false, out elementType)) + ThrowHelper.ThrowInvalidProgramException(); - public ByRefValueBase GetFieldAddress(FieldDesc field) - { - ThrowHelper.ThrowInvalidProgramException(); - return null; // unreached + if (elementType != _value._elementType) + ThrowHelper.ThrowInvalidProgramException(); + + if (field.Name == "_length") + return ValueTypeValue.FromInt32(_value._length / elementType.InstanceFieldSize.AsInt); + + Debug.Assert(field.Name == "_reference"); + return new ByRefValue(_value._bytes, _value._index); + } + + public ByRefValueBase GetFieldAddress(FieldDesc field) + { + ThrowHelper.ThrowInvalidProgramException(); + return null; // unreached + } } } @@ -2980,7 +2950,7 @@ public override bool TryStore(Value value) public override bool TryLoad(TypeDesc type, out Value value) { - if (!type.IsPrimitive + if (!type.IsValueType || ((MetadataType)type).InstanceFieldSize.AsInt > PointedToBytes.Length - PointedToOffset) { value = null; @@ -3247,15 +3217,9 @@ public void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, No builder.EmitBytes(_data); } - public bool TryGetReadOnlySpan(out ReadOnlySpanValue value) + public ByRefValue GetArrayData() { - if (((ArrayType)Type).ParameterType is MetadataType parameterType) - { - value = new ReadOnlySpanValue(parameterType, _data, 0, _data.Length); - return true; - } - value = null; - return false; + return new ByRefValue(_data, 0); } public bool IsKnownImmutable => _elementCount == 0; diff --git a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs index f3bd66ba8572a7..562c78d8aea586 100644 --- a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs +++ b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs @@ -53,6 +53,7 @@ private static int Main() TestSharedCode.Run(); TestSpan.Run(); TestReadOnlySpan.Run(); + TestRvaDataReads.Run(); TestStaticInterfaceMethod.Run(); TestConstrainedCall.Run(); TestTypeHandles.Run(); @@ -1235,6 +1236,51 @@ public static void Run() } } +class TestRvaDataReads +{ + static class GuidProvider + { + public static ref readonly Guid TheGuid1 + { + get + { + ReadOnlySpan data = [0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x9A, 0xAB, 0xBC, 0xCD, 0xDE, 0xEF, 0xF0, 0x00]; + return ref Unsafe.As(ref MemoryMarshal.GetReference(data)); + } + } + + public static ref readonly Guid TheGuid2 + { + get + { + ReadOnlySpan data = [0xDE, 0xEF, 0xF0, 0x00, 0x9A, 0xAB, 0xBC, 0xCD, 0x56, 0x67, 0x78, 0x89, 0x12, 0x23, 0x34, 0x45]; + return ref Unsafe.As(ref MemoryMarshal.GetReference(data)); + } + } + } + + struct TwoGuids + { + public Guid Guid1, Guid2; + } + + static class GuidReader + { + public static TwoGuids Value = new TwoGuids() + { + Guid1 = GuidProvider.TheGuid1, + Guid2 = GuidProvider.TheGuid2, + }; + } + + public static void Run() + { + Assert.IsPreinitialized(typeof(GuidReader)); + Assert.AreEqual(new Guid("45342312-6756-8978-9aab-bccddeeff000"), GuidReader.Value.Guid1); + Assert.AreEqual(new Guid("00f0efde-ab9a-cdbc-5667-788912233445"), GuidReader.Value.Guid2); + } +} + class TestStaticInterfaceMethod { interface IFoo @@ -2008,6 +2054,12 @@ public static void IsLazyInitialized(Type type, [CallerLineNumber] int line = 0) throw new Exception($"{type} is not lazy initialized. At line {line}."); } + public static void AreEqual(Guid v1, Guid v2, [CallerLineNumber] int line = 0) + { + if (v1 != v2) + throw new Exception($"Expect {v1}, but get {v2}. At line {line}."); + } + public static unsafe void AreEqual(void* v1, void* v2, [CallerLineNumber] int line = 0) { if (v1 != v2)