diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs index 87b93cd8e8f2e3..6cc9075708baac 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.InteropServices; using ILCompiler.DependencyAnalysis; @@ -402,6 +403,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack this; } - private sealed class StringInstance : ReferenceTypeValue + private sealed class StringInstance : ReferenceTypeValue, IHasInstanceFields { - private readonly string _value; + private readonly byte[] _value; + private string ValueAsString + { + get + { + FieldDesc firstCharField = Type.GetField("_firstChar"); + int startOffset = firstCharField.Offset.AsInt; + int length = _value.Length - startOffset - sizeof(char) /* terminating null */; + return new string(MemoryMarshal.Cast( + ((ReadOnlySpan)_value).Slice(startOffset, length))); + } + } public StringInstance(TypeDesc stringType, string value) : base(stringType) { - _value = value; + _value = ConstructStringInstance(stringType, value); + } + + private static byte[] ConstructStringInstance(TypeDesc stringType, ReadOnlySpan value) + { + int pointerSize = stringType.Context.Target.PointerSize; + var bytes = new byte[ + pointerSize /* MethodTable */ + + sizeof(int) /* length */ + + (value.Length * sizeof(char)) /* bytes */ + + sizeof(char) /* null terminator */]; + + FieldDesc lengthField = stringType.GetField("_stringLength"); + Debug.Assert(lengthField.FieldType.IsWellKnownType(WellKnownType.Int32) + && lengthField.Offset.AsInt == pointerSize); + new FieldAccessor(bytes).SetField(lengthField, ValueTypeValue.FromInt32(value.Length)); + + FieldDesc firstCharField = stringType.GetField("_firstChar"); + Debug.Assert(firstCharField.FieldType.IsWellKnownType(WellKnownType.Char) + && firstCharField.Offset.AsInt == pointerSize + sizeof(int) /* length */); + + value.CopyTo(MemoryMarshal.Cast(((Span)bytes).Slice(firstCharField.Offset.AsInt))); + + return bytes; } public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory) { - builder.EmitPointerReloc(factory.SerializedStringObject(_value)); + builder.EmitPointerReloc(factory.SerializedStringObject(ValueAsString)); } public override bool GetRawData(NodeFactory factory, out object data) { - data = factory.SerializedStringObject(_value); + data = factory.SerializedStringObject(ValueAsString); return true; } public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter) => this; + Value IHasInstanceFields.GetField(FieldDesc field) => new FieldAccessor(_value).GetField(field); + void IHasInstanceFields.SetField(FieldDesc field, Value value) => ThrowHelper.ThrowInvalidProgramException(); + ByRefValue IHasInstanceFields.GetFieldAddress(FieldDesc field) => new FieldAccessor(_value).GetFieldAddress(field); } #pragma warning disable CA1852 diff --git a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs index c2230c65d64bb7..6d6abed62ef9cb 100644 --- a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs +++ b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs @@ -45,6 +45,7 @@ private static int Main() TestGCInteraction.Run(); TestDuplicatedFields.Run(); TestInstanceDelegate.Run(); + TestStringFields.Run(); #else Console.WriteLine("Preinitialization is disabled in multimodule builds for now. Skipping test."); #endif @@ -913,6 +914,37 @@ public static void Run() } } +class TestStringFields +{ + class ClassAccessingLength + { + public static int Length = "Hello".Length; + } + + class ClassAccessingNull + { + public static int Length; + static ClassAccessingNull() + { + string myNull = null; + try + { + Length = myNull.Length; + } + catch (Exception) { } + } + } + + public static void Run() + { + Assert.IsPreinitialized(typeof(ClassAccessingLength)); + Assert.AreEqual(5, ClassAccessingLength.Length); + + Assert.IsLazyInitialized(typeof(ClassAccessingNull)); + Assert.AreEqual(0, ClassAccessingNull.Length); + } +} + static class Assert { [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern",