Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intrinsify CreateSpan in static constructor interpreter #81747

Merged
merged 4 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 186 additions & 3 deletions src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -895,11 +895,11 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
}

TypeDesc token = (TypeDesc)methodIL.GetObject(reader.ReadILToken());
if (token.IsGCPointer)
if (token.IsGCPointer || popped.Value is not ByRefValue byrefVal)
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
((ByRefValue)popped.Value).Initialize(token.GetElementSize().AsInt);
byrefVal.Initialize(token.GetElementSize().AsInt);
}
break;

Expand Down Expand Up @@ -1415,6 +1415,53 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
}
break;

case ILOpcode.ldind_i1:
case ILOpcode.ldind_u1:
case ILOpcode.ldind_i2:
case ILOpcode.ldind_u2:
case ILOpcode.ldind_i4:
case ILOpcode.ldind_u4:
MichalStrehovsky marked this conversation as resolved.
Show resolved Hide resolved
case ILOpcode.ldind_i8:
{
StackEntry entry = stack.Pop();
if (entry.Value is ByRefValue byRefVal)
{
switch (opcode)
{
case ILOpcode.ldind_i1:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(byRefVal.DereferenceAsSByte()));
break;
case ILOpcode.ldind_u1:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((byte)byRefVal.DereferenceAsSByte()));
break;
case ILOpcode.ldind_i2:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(byRefVal.DereferenceAsInt16()));
break;
case ILOpcode.ldind_u2:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((ushort)byRefVal.DereferenceAsInt16()));
break;
case ILOpcode.ldind_i4:
jkotas marked this conversation as resolved.
Show resolved Hide resolved
case ILOpcode.ldind_u4:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(byRefVal.DereferenceAsInt32()));
break;
MichalStrehovsky marked this conversation as resolved.
Show resolved Hide resolved
case ILOpcode.ldind_i8:
stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64(byRefVal.DereferenceAsInt64()));
break;
case ILOpcode.ldind_r4:
stack.Push(StackValueKind.Float, ValueTypeValue.FromDouble(byRefVal.DereferenceAsSingle()));
break;
case ILOpcode.ldind_r8:
stack.Push(StackValueKind.Float, ValueTypeValue.FromDouble(byRefVal.DereferenceAsDouble()));
break;
}
}
else
{
ThrowHelper.ThrowInvalidProgramException();
}
}
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
Expand All @@ -1428,12 +1475,20 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
return Status.Fail(methodIL.OwningMethod, "Control fell through");
}

private static ValueTypeValue NewUninitializedLocationValue(TypeDesc locationType)
private static BaseValueTypeValue NewUninitializedLocationValue(TypeDesc locationType)
{
if (locationType.IsGCPointer || locationType.IsByRef)
{
return null;
}
else if (locationType.IsByRefLike && locationType is MetadataType maybeReadOnlySpan
&& maybeReadOnlySpan.Module == locationType.Context.SystemModule
&& maybeReadOnlySpan.Name == "ReadOnlySpan`1"
&& maybeReadOnlySpan.Namespace == "System"
&& maybeReadOnlySpan.Instantiation[0] is MetadataType readOnlySpanElementType)
{
return new ReadOnlySpanValue(readOnlySpanElementType, Array.Empty<byte>());
}
else
{
Debug.Assert(locationType.IsValueType || locationType.IsPointer || locationType.IsFunctionPointer);
Expand All @@ -1460,6 +1515,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;
Expand Down Expand Up @@ -1938,6 +2020,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; }
Expand Down Expand Up @@ -2021,6 +2190,20 @@ public override bool GetRawData(NodeFactory factory, out object data)
data = null;
return false;
}

private ReadOnlySpan<byte> AsExactByteCount(int count)
{
if (PointedToOffset + count > PointedToBytes.Length)
ThrowHelper.ThrowInvalidProgramException();
return new ReadOnlySpan<byte>(PointedToBytes, PointedToOffset, count);
}

public sbyte DereferenceAsSByte() => (sbyte)AsExactByteCount(1)[0];
public short DereferenceAsInt16() => BitConverter.ToInt16(AsExactByteCount(2));
public int DereferenceAsInt32() => BitConverter.ToInt32(AsExactByteCount(4));
MichalStrehovsky marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1000,6 +1001,60 @@ public static void Run()
}
}

class TestReadOnlySpan
{
class SimpleReadOnlySpanAccess
{
private static ReadOnlySpan<int> Ints => new int[] { 5, 6, 7, 8 };

public /* not readonly on purpose */ static int Sum;

static SimpleReadOnlySpanAccess()
{
ReadOnlySpan<int> val = Ints;
Sum = val[0] + val[1] + val[2] + val[3];
}
}

class OutOfRangeAccess
{
private static ReadOnlySpan<int> Ints => new int[] { 5, 6, 7, 8 };

public readonly static int Sum;

static OutOfRangeAccess()
{
ReadOnlySpan<int> val = Ints;
Sum = val[4];
}
}

class DefaultInstanceAccess
{
public readonly static int Sum;

static DefaultInstanceAccess()
{
ReadOnlySpan<int> 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",
Expand Down