diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs index 989d4782402ac..63fd0f132fcdc 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs @@ -37,6 +37,7 @@ public class TypePreinit private readonly TypePreinitializationPolicy _policy; private readonly Dictionary _fieldValues = new Dictionary(); private readonly Dictionary _internedStrings = new Dictionary(); + private readonly Dictionary _internedTypes = new Dictionary(); private TypePreinit(MetadataType owningType, CompilationModuleGroup compilationGroup, ILProvider ilProvider, TypePreinitializationPolicy policy) { @@ -167,6 +168,9 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack(); recursionProtect.Push(methodIL.OwningMethod); @@ -524,6 +539,25 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack= ILOpcode.ldind_i1 and <= ILOpcode.ldind_i) + { + // In the interpreter memory model, there's no conversion from a byref to an integer. + // Roslyn however sometimes emits a sequence of conv_u followed by ldind and we can + // have a narrow path to handle that one. + stack.Push(popped); + goto again; + } else { return Status.Fail(methodIL.OwningMethod, opcode); @@ -787,16 +831,26 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack reader.ReadILByte(), - ILOpcode.ldloca => reader.ReadILUInt16(), + ILOpcode.ldloca_s or ILOpcode.ldarga_s => reader.ReadILByte(), + ILOpcode.ldloca or ILOpcode.ldarga => reader.ReadILUInt16(), _ => throw new NotImplementedException(), // Unreachable }; - if (index >= locals.Length) + Value[] storage = opcode is ILOpcode.ldloca or ILOpcode.ldloca_s ? locals : parameters; + if (index >= storage.Length) { ThrowHelper.ThrowInvalidProgramException(); } - Value localValue = locals[index]; + Value localValue = storage[index]; if (localValue == null || !localValue.TryCreateByRef(out Value byrefValue)) { return Status.Fail(methodIL.OwningMethod, opcode); @@ -1417,6 +1474,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack ILOpcode.ldind_i1, + TypeFlags.Boolean or TypeFlags.Byte => ILOpcode.ldind_u1, + TypeFlags.Int16 => ILOpcode.ldind_i2, + TypeFlags.Char or TypeFlags.UInt16 => ILOpcode.ldind_u2, + TypeFlags.Int32 => ILOpcode.ldind_i4, + TypeFlags.UInt32 => ILOpcode.ldind_u4, + TypeFlags.Int64 or TypeFlags.UInt64 => ILOpcode.ldind_i8, + _ => ILOpcode.ldobj, + }; + + if (opcode == ILOpcode.ldobj) + { + return Status.Fail(methodIL.OwningMethod, opcode); + } + } + StackEntry entry = stack.Pop(); if (entry.Value is ByRefValue byRefVal) { @@ -1464,10 +1543,56 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack ILOpcode.stind_i1, + TypeFlags.Int16 or TypeFlags.Char or TypeFlags.UInt16 => ILOpcode.stind_i2, + TypeFlags.Int32 or TypeFlags.UInt32 => ILOpcode.stind_i4, + TypeFlags.Int64 or TypeFlags.UInt64 => ILOpcode.stind_i8, + _ => ILOpcode.stobj, + }; + + if (opcode == ILOpcode.stobj) + { + return Status.Fail(methodIL.OwningMethod, opcode); + } + } + + Value val = opcode switch + { + ILOpcode.stind_i1 => stack.PopIntoLocation(context.GetWellKnownType(WellKnownType.Byte)), + ILOpcode.stind_i2 => stack.PopIntoLocation(context.GetWellKnownType(WellKnownType.UInt16)), + ILOpcode.stind_i4 => stack.PopIntoLocation(context.GetWellKnownType(WellKnownType.UInt32)), + ILOpcode.stind_i8 => stack.PopIntoLocation(context.GetWellKnownType(WellKnownType.UInt64)), + _ => throw new NotImplementedException() + }; + + StackEntry location = stack.Pop(); + if (location.ValueKind != StackValueKind.ByRef) + ThrowHelper.ThrowInvalidProgramException(); + + byte[] dest = ((ByRefValue)location.Value).PointedToBytes; + int destOffset = ((ByRefValue)location.Value).PointedToOffset; + byte[] src = ((ValueTypeValue)val).InstanceBytes; + if (destOffset + src.Length > dest.Length) + ThrowHelper.ThrowInvalidProgramException(); + Array.Copy(src, 0, dest, destOffset, src.Length); + } + break; + case ILOpcode.constrained: - // Fallthrough. If this is ever implemented, make sure delegates to static virtual methods - // are also handled. We currently assume the frozen delegate will not be to a static - // virtual interface method. + constrainedType = methodIL.GetObject(reader.ReadILToken()) as TypeDesc; + goto again; + default: return Status.Fail(methodIL.OwningMethod, opcode); } @@ -1477,19 +1602,31 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack()); + return new ReadOnlySpanValue(readOnlySpanElementType, Array.Empty(), 0, 0); } else { @@ -1498,7 +1635,7 @@ private static BaseValueTypeValue NewUninitializedLocationValue(TypeDesc locatio } } - private static bool TryHandleIntrinsicCall(MethodDesc method, Value[] parameters, out Value retVal) + private bool TryHandleIntrinsicCall(MethodDesc method, Value[] parameters, out Value retVal) { retVal = default; @@ -1531,7 +1668,7 @@ private static bool TryHandleIntrinsicCall(MethodDesc method, Value[] parameters byte[] rvaData = Internal.TypeSystem.Ecma.EcmaFieldExtensions.GetFieldRvaData(createSpanEcmaField); if (rvaData.Length % elementSize != 0) return false; - retVal = new ReadOnlySpanValue(elementType, rvaData); + retVal = new ReadOnlySpanValue(elementType, rvaData, 0, rvaData.Length); return true; } return false; @@ -1544,6 +1681,33 @@ private static bool TryHandleIntrinsicCall(MethodDesc method, Value[] parameters return spanRef.TryAccessElement(spanIndex.AsInt32(), out retVal); } return false; + case "GetTypeFromHandle" when method.OwningType is MetadataType typeType + && typeType.Name == "Type" && typeType.Namespace == "System" + && typeType.Module == typeType.Context.SystemModule + && parameters[0] is RuntimeTypeHandleValue typeHandle: + { + if (!_internedTypes.TryGetValue(typeHandle.Type, out RuntimeTypeValue runtimeType)) + { + _internedTypes.Add(typeHandle.Type, runtimeType = new RuntimeTypeValue(typeHandle.Type)); + } + retVal = runtimeType; + return true; + } + case "op_Equality" when method.OwningType is MetadataType typeType + && typeType.Name == "Type" && typeType.Namespace == "System" + && typeType.Module == typeType.Context.SystemModule + && (parameters[0] is RuntimeTypeValue || parameters[1] is RuntimeTypeValue): + { + retVal = ValueTypeValue.FromSByte(parameters[0] == parameters[1] ? (sbyte)1 : (sbyte)0); + return true; + } + case "get_IsSupported" when method.OwningType is MetadataType sse2Type + && sse2Type.Name == "Sse2": + { + // TODO: hacky + retVal = ValueTypeValue.FromSByte(sse2Type.Context.Target.Architecture == TargetArchitecture.X64 ? (sbyte)1 : (sbyte)0); + return true; + } } return false; @@ -2023,15 +2187,73 @@ public override bool GetRawData(NodeFactory factory, out object data) } } + private sealed class RuntimeTypeHandleValue : BaseValueTypeValue, IInternalModelingOnlyValue + { + public TypeDesc Type { get; } + + public RuntimeTypeHandleValue(TypeDesc type) + { + Type = type; + } + + public override int Size => Type.Context.Target.PointerSize; + + public override bool Equals(Value value) + { + if (!(value is RuntimeTypeHandleValue)) + { + ThrowHelper.ThrowInvalidProgramException(); + } + + return Type == ((RuntimeTypeHandleValue)value).Type; + } + + public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory) + { + throw new NotSupportedException(); + } + + public override bool GetRawData(NodeFactory factory, out object data) + { + data = null; + return false; + } + } + + private sealed class RuntimeTypeValue : ReferenceTypeValue, IInternalModelingOnlyValue + { + public TypeDesc TypeRepresented { get; } + + public RuntimeTypeValue(TypeDesc type) + : base(type.Context.SystemModule.GetKnownType("System", "RuntimeType")) + { + TypeRepresented = type; + } + + public override bool GetRawData(NodeFactory factory, out object data) + { + data = null; + return false; + } + 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 { private readonly MetadataType _elementType; private readonly byte[] _bytes; + private readonly int _index; + private readonly int _length; - public ReadOnlySpanValue(MetadataType elementType, byte[] bytes) + public ReadOnlySpanValue(MetadataType elementType, byte[] bytes, int index, int length) { + Debug.Assert(index <= bytes.Length); + Debug.Assert(length <= bytes.Length - index); _elementType = elementType; _bytes = bytes; + _index = index; + _length = length; } public override int Size => 2 * _elementType.Context.Target.PointerSize; @@ -2063,20 +2285,26 @@ public override Value Clone() public override bool TryCreateByRef(out Value value) { - value = new ReadOnlySpanReferenceValue(_elementType, _bytes); + value = new ReadOnlySpanReferenceValue(_elementType, _bytes, _index, _length); return true; } } - private sealed class ReadOnlySpanReferenceValue : Value + private sealed class ReadOnlySpanReferenceValue : Value, IHasInstanceFields { private readonly MetadataType _elementType; private readonly byte[] _bytes; + private readonly int _index; + private readonly int _length; - public ReadOnlySpanReferenceValue(MetadataType elementType, byte[] bytes) + public ReadOnlySpanReferenceValue(MetadataType elementType, byte[] bytes, int index, int length) { + Debug.Assert(index <= bytes.Length); + Debug.Assert(length <= bytes.Length - index); _elementType = elementType; _bytes = bytes; + _index = index; + _length = length; } public override bool Equals(Value value) @@ -2101,13 +2329,29 @@ public override bool GetRawData(NodeFactory factory, out object data) public bool TryAccessElement(int index, out Value value) { value = default; - int limit = _bytes.Length / _elementType.InstanceFieldSize.AsInt; + int limit = _length / _elementType.InstanceFieldSize.AsInt; if (index >= limit) return false; - value = new ByRefValue(_bytes, index * _elementType.InstanceFieldSize.AsInt); + value = new ByRefValue(_bytes, _index + index * _elementType.InstanceFieldSize.AsInt); return true; } + + public void SetField(FieldDesc field, Value value) => ThrowHelper.ThrowInvalidProgramException(); + + public Value GetField(FieldDesc field) + { + if (field.Name != "_length") + ThrowHelper.ThrowInvalidProgramException(); + + return ValueTypeValue.FromInt32(_length / _elementType.InstanceFieldSize.AsInt); + } + + public ByRefValue GetFieldAddress(FieldDesc field) + { + ThrowHelper.ThrowInvalidProgramException(); + return null; // unreached + } } private sealed class MethodPointerValue : BaseValueTypeValue, IInternalModelingOnlyValue diff --git a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs index ed28083009839..ca5ab02d0a725 100644 --- a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs +++ b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs @@ -50,6 +50,9 @@ private static int Main() TestStringFields.Run(); TestSharedCode.Run(); TestReadOnlySpan.Run(); + TestStaticInterfaceMethod.Run(); + TestConstrainedCall.Run(); + TestTypeHandles.Run(); #else Console.WriteLine("Preinitialization is disabled in multimodule builds for now. Skipping test."); #endif @@ -1054,6 +1057,18 @@ static DefaultInstanceAccess() } } + class MoreOperations + { + public readonly static int Length; + + private static ReadOnlySpan Ints => new int[] { 5, 6, 7, 8 }; + + static MoreOperations() + { + Length = Ints.Length; + } + } + public static void Run() { Assert.IsPreinitialized(typeof(SimpleReadOnlySpanAccess)); @@ -1066,9 +1081,118 @@ public static void Run() Assert.IsLazyInitialized(typeof(DefaultInstanceAccess)); if (SimpleReadOnlySpanAccess.Sum == 1000) // never true DefaultInstanceAccess.Sum.ToString(); // make sure cctor is looked at + + Assert.IsPreinitialized(typeof(MoreOperations)); + Assert.AreEqual(4, MoreOperations.Length); } } +class TestStaticInterfaceMethod +{ + interface IFoo + { + static virtual int GetCookie1() => 42; + static virtual int GetCookie2() => 0; + } + + struct Foo : IFoo + { + static int IFoo.GetCookie2() => 100; + } + + class SimpleStaticInterfaceMethodCall + { + public static readonly int s_value1 = Compute1(); + public static readonly int s_value2 = Compute2(); + + static int Compute1() where T : IFoo => T.GetCookie1(); + static int Compute2() where T : IFoo => T.GetCookie2(); + } + + public static void Run() + { + Assert.IsPreinitialized(typeof(SimpleStaticInterfaceMethodCall)); + Assert.AreEqual(42, SimpleStaticInterfaceMethodCall.s_value1); + Assert.AreEqual(100, SimpleStaticInterfaceMethodCall.s_value2); + } +} + +class TestConstrainedCall +{ + interface IFoo + { + int Frob(); + } + + struct Foo : IFoo + { + public int val; + + int IFoo.Frob() + { + val = 42; + return 100; + } + } + + static int Call(ref T inst) where T : IFoo => inst.Frob(); + + class ConstrainedCall + { + public static Foo s_f; + public static int s_i; + + static ConstrainedCall() + { + Foo f = default; + s_i = Call(ref f); + s_f = f; + } + } + + public static void Run() + { + Assert.IsPreinitialized(typeof(ConstrainedCall)); + Assert.AreEqual(100, ConstrainedCall.s_i); + Assert.AreEqual(42, ConstrainedCall.s_f.val); + } +} + +class TestTypeHandles +{ + class Foo + { + public static bool IsChar = typeof(T) == typeof(char); + public static bool IsBool = typeof(T) == typeof(bool); + } + + class CharHolder + { + public static readonly Type Type = typeof(char); + } + + class IsChar + { + public static bool Is = typeof(char) == CharHolder.Type; + } + + public static void Run() + { + Assert.IsPreinitialized(typeof(Foo)); + Assert.True(Foo.IsChar); + Assert.True(!Foo.IsBool); + + Assert.IsPreinitialized(typeof(Foo)); + Assert.True(!Foo.IsChar); + Assert.True(Foo.IsBool); + + Assert.IsLazyInitialized(typeof(CharHolder)); + Assert.IsLazyInitialized(typeof(IsChar)); + Assert.True(IsChar.Is); + } +} + + static class Assert { [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern",