From 00ca7be02f465b0eaf252b4b9192aa28b0c208fa Mon Sep 17 00:00:00 2001 From: Robin Sue Date: Wed, 24 Jul 2024 23:44:46 +0200 Subject: [PATCH 1/2] Preinit: Support string manipulation Makes it possible to allocate and preinitialize strings. Extends arithmetic support to handle `nint operator int32` operations Adds xor / not / shr support and extends casting for floating point types --- .../src/System/String.NativeAot.cs | 1 + .../Compiler/TypePreinit.cs | 145 ++++++++++++++++-- .../Preinitialization/Preinitialization.cs | 22 +++ 3 files changed, 154 insertions(+), 14 deletions(-) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/String.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/String.NativeAot.cs index a8efc76018a30d..6a7d4b9f3eaecb 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/String.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/String.NativeAot.cs @@ -18,6 +18,7 @@ public partial class String [Intrinsic] public static readonly string Empty = ""; + [Intrinsic] internal static unsafe string FastAllocateString(int length) { // We allocate one extra char as an interop convenience so that our strings are null- diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs index 8b86a5b526cb8d..00c5e1eaa85702 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs @@ -379,11 +379,11 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack(); recursionProtect.Push(methodIL.OwningMethod); Status callResult = TryScanMethod(method, methodParams, recursionProtect, ref instructionCounter, out retVal); + recursionProtect.Pop(); if (!callResult.IsSuccessful) { - recursionProtect.Pop(); return callResult; } - recursionProtect.Pop(); } if (!methodSig.ReturnType.IsVoid) @@ -665,13 +664,11 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack(); recursionProtect.Push(methodIL.OwningMethod); Status ctorCallResult = TryScanMethod(ctor, ctorParameters, recursionProtect, ref instructionCounter, out _); + recursionProtect.Pop(); if (!ctorCallResult.IsSuccessful) { - recursionProtect.Pop(); return ctorCallResult; } - - recursionProtect.Pop(); } stack.PushFromLocation(owningType, instance); @@ -824,6 +821,8 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack= ILOpcode.ldind_i1 and <= ILOpcode.ldind_ref) or ILOpcode.ldobj)) { // In the interpreter memory model, there's no conversion from a byref to an integer. @@ -1379,13 +1422,29 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack value1.Value.AsInt32() | value2.Value.AsInt32(), + ILOpcode.xor => value1.Value.AsInt32() ^ value2.Value.AsInt32(), ILOpcode.shl => value1.Value.AsInt32() << value2.Value.AsInt32(), + ILOpcode.shr => value1.Value.AsInt32() >> value2.Value.AsInt32(), ILOpcode.add => value1.Value.AsInt32() + value2.Value.AsInt32(), ILOpcode.sub => value1.Value.AsInt32() - value2.Value.AsInt32(), ILOpcode.and => value1.Value.AsInt32() & value2.Value.AsInt32(), @@ -1433,6 +1505,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack value1.Value.AsInt64() | value2.Value.AsInt64(), + ILOpcode.xor => value1.Value.AsInt64() ^ value2.Value.AsInt64(), ILOpcode.add => value1.Value.AsInt64() + value2.Value.AsInt64(), ILOpcode.sub => value1.Value.AsInt64() - value2.Value.AsInt64(), ILOpcode.and => value1.Value.AsInt64() & value2.Value.AsInt64(), @@ -1451,7 +1524,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack value1.Value.AsInt64() << value2.Value.AsInt32(), + ILOpcode.shr => value1.Value.AsInt64() >> value2.Value.AsInt32(), + _ => throw new NotImplementedException(), // unreachable + }; stack.Push(isNint ? StackValueKind.NativeInt : StackValueKind.Int64, ValueTypeValue.FromInt64(result)); } else if ((value1.ValueKind == StackValueKind.ByRef && value2.ValueKind != StackValueKind.ByRef) @@ -1679,6 +1757,8 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack(src.PointedToBytes, src.PointedToOffset, length); + var dstSpan = new Span(dest.PointedToBytes, dest.PointedToOffset, length); + srcSpan.CopyTo(dstSpan); + return true; + } + return false; + case "IsReferenceOrContainsReferences": + if (method.OwningType is MetadataType rtType + && rtType.Name == "RuntimeHelpers" && rtType.Namespace == "System.Runtime.CompilerServices" + && rtType.Module == rtType.Context.SystemModule + && method.Instantiation.Length == 1) + { + var type = method.Instantiation[0]; + bool result = type.IsGCPointer || (type is DefType { ContainsGCPointers: true }); + retVal = ValueTypeValue.FromSByte(result ? (sbyte)1 : (sbyte)0); + return true; + } + return false; + case "FastAllocateString": + if (method.OwningType is MetadataType stringType + && stringType.Name == "String" && stringType.Namespace == "System" + && stringType.Module == stringType.Context.SystemModule + && parameters[0] is ValueTypeValue strSize) + { + retVal = new StringInstance(context.GetWellKnownType(WellKnownType.String), new string('\0', strSize.AsInt32())); + return true; + } + return false; case "InitializeArray": if (method.OwningType is MetadataType mdType && mdType.Name == "RuntimeHelpers" && mdType.Namespace == "System.Runtime.CompilerServices" diff --git a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs index 78223dca967fe4..e0dd06ae29c5a9 100644 --- a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs +++ b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs @@ -17,6 +17,7 @@ private static int Main() #if !MULTIMODULE_BUILD TestHardwareIntrinsics.Run(); TestLdstr.Run(); + TestStringManip.Run(); TestException.Run(); TestThreadStaticNotInitialized.Run(); TestUntouchedThreadStaticInitialized.Run(); @@ -122,6 +123,27 @@ public static void Run() } } +class TestStringManip +{ + static string testString; + + static TestStringManip() + { + string test = ""; + for (int i = 0; i < 3; i++) + { + test += "A"; + } + testString = test; + } + + public static void Run() + { + Assert.IsPreinitialized(typeof(TestStringManip)); + Assert.AreSame("AAA", testString); + } +} + class TestException { static bool s_wasThrown; From a834b1cc6575619e59d4353aba2a449309333046 Mon Sep 17 00:00:00 2001 From: Robin Sue Date: Thu, 25 Jul 2024 04:27:32 +0200 Subject: [PATCH 2/2] Revise overflow check --- .../ILCompiler.Compiler/Compiler/TypePreinit.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs index 00c5e1eaa85702..693b9ca6385024 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs @@ -1958,7 +1958,21 @@ private bool TryHandleIntrinsicCall(TypeSystemContext context, MethodDesc method && parameters[1] is ByRefValue src && parameters[2] is ValueTypeValue len) { - int length = context.Target.PointerSize == 8 ? checked ((int)len.AsInt64()) : len.AsInt32(); + int length; + if (context.Target.PointerSize == 8) + { + long longLength = len.AsInt64(); + if (longLength > int.MaxValue || longLength < int.MinValue) + { + return false; + } + + length = (int)longLength; + } + else + { + length = len.AsInt32(); + } var srcSpan = new Span(src.PointedToBytes, src.PointedToOffset, length); var dstSpan = new Span(dest.PointedToBytes, dest.PointedToOffset, length); srcSpan.CopyTo(dstSpan);