From a7a54153fd800f019f956c54c0c212289d3f5bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 7 Feb 2023 16:34:18 +0900 Subject: [PATCH 1/3] Intrinsify `CreateSpan` in static constructor interpreter --- .../Compiler/TypePreinit.cs | 158 +++++++++++++++++- .../Preinitialization/Preinitialization.cs | 55 ++++++ 2 files changed, 210 insertions(+), 3 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs index aee735bf2d863..ace788daa04c9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs @@ -895,11 +895,11 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack()); + } else { Debug.Assert(locationType.IsValueType || locationType.IsPointer || locationType.IsFunctionPointer); @@ -1460,6 +1489,33 @@ private static bool TryHandleIntrinsicCall(MethodDesc method, Value[] parameters return array.TryInitialize(rvaData); } return false; + case "CreateSpan": + if (method.OwningType is MetadataType createSpanType + && createSpanType.Name == "RuntimeHelpers" && createSpanType.Namespace == "System.Runtime.CompilerServices" + && createSpanType.Module == createSpanType.Context.SystemModule + && parameters[0] is RuntimeFieldHandleValue createSpanFieldHandle + && createSpanFieldHandle.Field.IsStatic && createSpanFieldHandle.Field.HasRva + && createSpanFieldHandle.Field is Internal.TypeSystem.Ecma.EcmaField createSpanEcmaField + && method.Instantiation[0].IsValueType) + { + var elementType = (MetadataType)method.Instantiation[0]; + int elementSize = elementType.InstanceFieldSize.AsInt; + byte[] rvaData = Internal.TypeSystem.Ecma.EcmaFieldExtensions.GetFieldRvaData(createSpanEcmaField); + if (rvaData.Length % elementSize != 0) + return false; + retVal = new ReadOnlySpanValue(elementType, rvaData); + 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; } return false; @@ -1938,6 +1994,93 @@ public override bool GetRawData(NodeFactory factory, out object data) } } + private sealed class ReadOnlySpanValue : BaseValueTypeValue, IInternalModelingOnlyValue + { + private readonly MetadataType _elementType; + private readonly byte[] _bytes; + + public ReadOnlySpanValue(MetadataType elementType, byte[] bytes) + { + _elementType = elementType; + _bytes = bytes; + } + + public override int Size => 2 * _elementType.Context.Target.PointerSize; + + public override bool Equals(Value value) + { + // ceq instruction on ReadOnlySpans is hard to support. + // We should not see it in the first place. + ThrowHelper.ThrowInvalidProgramException(); + return false; + } + + 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; + } + + public override Value Clone() + { + // ReadOnlySpan is immutable and there's no way for the data to escape + return this; + } + + public override bool TryCreateByRef(out Value value) + { + value = new ReadOnlySpanReferenceValue(_elementType, _bytes); + return true; + } + } + + private sealed class ReadOnlySpanReferenceValue : Value + { + private readonly MetadataType _elementType; + private readonly byte[] _bytes; + + public ReadOnlySpanReferenceValue(MetadataType elementType, byte[] bytes) + { + _elementType = elementType; + _bytes = bytes; + } + + public override bool Equals(Value value) + { + // ceq instruction on refs to ReadOnlySpans is hard to support. + // We should not see it in the first place. + ThrowHelper.ThrowInvalidProgramException(); + return false; + } + + 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; + } + + public bool TryAccessElement(int index, out Value value) + { + value = default; + int limit = _bytes.Length / _elementType.InstanceFieldSize.AsInt; + if (index >= limit) + return false; + + value = new ByRefValue(_bytes, index * _elementType.InstanceFieldSize.AsInt); + return true; + } + } + private sealed class MethodPointerValue : BaseValueTypeValue, IInternalModelingOnlyValue { public MethodDesc PointedToMethod { get; } @@ -2021,6 +2164,15 @@ public override bool GetRawData(NodeFactory factory, out object data) data = null; return false; } + + private ReadOnlySpan AsExactByteCount(int count) + { + if (PointedToOffset + count > PointedToBytes.Length) + ThrowHelper.ThrowInvalidProgramException(); + return new ReadOnlySpan(PointedToBytes, PointedToOffset, count); + } + + public int DereferenceAsInt32() => BitConverter.ToInt32(AsExactByteCount(4)); } private abstract class ReferenceTypeValue : Value diff --git a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs index 803e6d179467e..e87d6f44f4673 100644 --- a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs +++ b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs @@ -48,6 +48,7 @@ private static int Main() TestInstanceDelegate.Run(); TestStringFields.Run(); TestSharedCode.Run(); + TestReadOnlySpan.Run(); #else Console.WriteLine("Preinitialization is disabled in multimodule builds for now. Skipping test."); #endif @@ -1000,6 +1001,60 @@ public static void Run() } } +class TestReadOnlySpan +{ + class SimpleReadOnlySpanAccess + { + private static ReadOnlySpan Ints => new int[] { 5, 6, 7, 8 }; + + public /* not readonly on purpose */ static int Sum; + + static SimpleReadOnlySpanAccess() + { + ReadOnlySpan val = Ints; + Sum = val[0] + val[1] + val[2] + val[3]; + } + } + + class OutOfRangeAccess + { + private static ReadOnlySpan Ints => new int[] { 5, 6, 7, 8 }; + + public readonly static int Sum; + + static OutOfRangeAccess() + { + ReadOnlySpan val = Ints; + Sum = val[4]; + } + } + + class DefaultInstanceAccess + { + public readonly static int Sum; + + static DefaultInstanceAccess() + { + ReadOnlySpan val = default; + Sum = val[0]; + } + } + + public static void Run() + { + Assert.IsPreinitialized(typeof(SimpleReadOnlySpanAccess)); + Assert.AreEqual(26, SimpleReadOnlySpanAccess.Sum); + + Assert.IsLazyInitialized(typeof(OutOfRangeAccess)); + if (SimpleReadOnlySpanAccess.Sum == 1000) // never true + OutOfRangeAccess.Sum.ToString(); // make sure cctor is looked at + + Assert.IsLazyInitialized(typeof(DefaultInstanceAccess)); + if (SimpleReadOnlySpanAccess.Sum == 1000) // never true + DefaultInstanceAccess.Sum.ToString(); // make sure cctor is looked at + } +} + static class Assert { [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", From b7e4fb814b1de15d26983750d30c1f3dec93e2c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 13 Feb 2023 09:57:44 +0900 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Jan Kotas --- .../Compiler/TypePreinit.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs index ace788daa04c9..68c5b25276837 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs @@ -1415,18 +1415,44 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack Date: Mon, 13 Feb 2023 10:58:04 +0900 Subject: [PATCH 3/3] Update src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs Co-authored-by: Jan Kotas --- .../tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs index 68c5b25276837..61adf5a2d5559 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs @@ -2198,7 +2198,12 @@ private ReadOnlySpan AsExactByteCount(int count) return new ReadOnlySpan(PointedToBytes, PointedToOffset, count); } + public sbyte DereferenceAsSByte() => (sbyte)AsExactByteCount(1)[0]; + public short DereferenceAsInt16() => BitConverter.ToInt16(AsExactByteCount(2)); public int DereferenceAsInt32() => BitConverter.ToInt32(AsExactByteCount(4)); + public long DereferenceAsInt64() => BitConverter.ToInt64(AsExactByteCount(8)); + public float DereferenceAsSingle() => BitConverter.ToSingle(AsExactByteCount(4)); + public double DereferenceAsDouble() => BitConverter.ToDouble(AsExactByteCount(8)); } private abstract class ReferenceTypeValue : Value