Skip to content

Commit 8020db5

Browse files
Extend preinitialization interpreter to support calling string::Length (#78680)
* Support `callvirt` to a non-virtual method * Support accessing string's fields
1 parent d7aabd5 commit 8020db5

File tree

2 files changed

+88
-5
lines changed

2 files changed

+88
-5
lines changed

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Diagnostics;
7+
using System.Runtime.InteropServices;
78

89
using ILCompiler.DependencyAnalysis;
910

@@ -402,6 +403,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
402403
break;
403404

404405
case ILOpcode.call:
406+
case ILOpcode.callvirt:
405407
{
406408
MethodDesc method = (MethodDesc)methodIL.GetObject(reader.ReadILToken());
407409
MethodSignature methodSig = method.Signature;
@@ -427,6 +429,13 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
427429
methodParams[i] = stack.PopIntoLocation(GetArgType(method, i));
428430
}
429431

432+
if (opcode == ILOpcode.callvirt)
433+
{
434+
// Only support non-virtual methods for now + we don't emulate NRE on null this
435+
if (method.IsVirtual || methodParams[0] == null)
436+
return Status.Fail(methodIL.OwningMethod, opcode);
437+
}
438+
430439
Value retVal;
431440
if (!method.IsIntrinsic || !TryHandleIntrinsicCall(method, methodParams, out retVal))
432441
{
@@ -598,6 +607,11 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
598607
return Status.Fail(methodIL.OwningMethod, opcode, "Reference field");
599608
}
600609

610+
if (field.FieldType.IsByRef)
611+
{
612+
return Status.Fail(methodIL.OwningMethod, opcode, "Byref field");
613+
}
614+
601615
var settableInstance = instance.Value as IHasInstanceFields;
602616
if (settableInstance == null)
603617
{
@@ -2259,28 +2273,65 @@ public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory f
22592273
public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter) => this;
22602274
}
22612275

2262-
private sealed class StringInstance : ReferenceTypeValue
2276+
private sealed class StringInstance : ReferenceTypeValue, IHasInstanceFields
22632277
{
2264-
private readonly string _value;
2278+
private readonly byte[] _value;
22652279

2280+
private string ValueAsString
2281+
{
2282+
get
2283+
{
2284+
FieldDesc firstCharField = Type.GetField("_firstChar");
2285+
int startOffset = firstCharField.Offset.AsInt;
2286+
int length = _value.Length - startOffset - sizeof(char) /* terminating null */;
2287+
return new string(MemoryMarshal.Cast<byte, char>(
2288+
((ReadOnlySpan<byte>)_value).Slice(startOffset, length)));
2289+
}
2290+
}
22662291
public StringInstance(TypeDesc stringType, string value)
22672292
: base(stringType)
22682293
{
2269-
_value = value;
2294+
_value = ConstructStringInstance(stringType, value);
2295+
}
2296+
2297+
private static byte[] ConstructStringInstance(TypeDesc stringType, ReadOnlySpan<char> value)
2298+
{
2299+
int pointerSize = stringType.Context.Target.PointerSize;
2300+
var bytes = new byte[
2301+
pointerSize /* MethodTable */
2302+
+ sizeof(int) /* length */
2303+
+ (value.Length * sizeof(char)) /* bytes */
2304+
+ sizeof(char) /* null terminator */];
2305+
2306+
FieldDesc lengthField = stringType.GetField("_stringLength");
2307+
Debug.Assert(lengthField.FieldType.IsWellKnownType(WellKnownType.Int32)
2308+
&& lengthField.Offset.AsInt == pointerSize);
2309+
new FieldAccessor(bytes).SetField(lengthField, ValueTypeValue.FromInt32(value.Length));
2310+
2311+
FieldDesc firstCharField = stringType.GetField("_firstChar");
2312+
Debug.Assert(firstCharField.FieldType.IsWellKnownType(WellKnownType.Char)
2313+
&& firstCharField.Offset.AsInt == pointerSize + sizeof(int) /* length */);
2314+
2315+
value.CopyTo(MemoryMarshal.Cast<byte, char>(((Span<byte>)bytes).Slice(firstCharField.Offset.AsInt)));
2316+
2317+
return bytes;
22702318
}
22712319

22722320
public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory)
22732321
{
2274-
builder.EmitPointerReloc(factory.SerializedStringObject(_value));
2322+
builder.EmitPointerReloc(factory.SerializedStringObject(ValueAsString));
22752323
}
22762324

22772325
public override bool GetRawData(NodeFactory factory, out object data)
22782326
{
2279-
data = factory.SerializedStringObject(_value);
2327+
data = factory.SerializedStringObject(ValueAsString);
22802328
return true;
22812329
}
22822330

22832331
public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter) => this;
2332+
Value IHasInstanceFields.GetField(FieldDesc field) => new FieldAccessor(_value).GetField(field);
2333+
void IHasInstanceFields.SetField(FieldDesc field, Value value) => ThrowHelper.ThrowInvalidProgramException();
2334+
ByRefValue IHasInstanceFields.GetFieldAddress(FieldDesc field) => new FieldAccessor(_value).GetFieldAddress(field);
22842335
}
22852336

22862337
#pragma warning disable CA1852

src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ private static int Main()
4545
TestGCInteraction.Run();
4646
TestDuplicatedFields.Run();
4747
TestInstanceDelegate.Run();
48+
TestStringFields.Run();
4849
#else
4950
Console.WriteLine("Preinitialization is disabled in multimodule builds for now. Skipping test.");
5051
#endif
@@ -913,6 +914,37 @@ public static void Run()
913914
}
914915
}
915916

917+
class TestStringFields
918+
{
919+
class ClassAccessingLength
920+
{
921+
public static int Length = "Hello".Length;
922+
}
923+
924+
class ClassAccessingNull
925+
{
926+
public static int Length;
927+
static ClassAccessingNull()
928+
{
929+
string myNull = null;
930+
try
931+
{
932+
Length = myNull.Length;
933+
}
934+
catch (Exception) { }
935+
}
936+
}
937+
938+
public static void Run()
939+
{
940+
Assert.IsPreinitialized(typeof(ClassAccessingLength));
941+
Assert.AreEqual(5, ClassAccessingLength.Length);
942+
943+
Assert.IsLazyInitialized(typeof(ClassAccessingNull));
944+
Assert.AreEqual(0, ClassAccessingNull.Length);
945+
}
946+
}
947+
916948
static class Assert
917949
{
918950
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern",

0 commit comments

Comments
 (0)