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

Add more static preinitialization support #92739

Merged
merged 3 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
182 changes: 151 additions & 31 deletions src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,8 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method

instance = new DelegateInstance(owningType, pointedMethod, firstParameter, allocSite);
}
else if (TryGetReadOnlySpanElementType(owningType, out MetadataType readOnlySpanElementType)
else if ((TryGetSpanElementType(owningType, isReadOnlySpan: true, out MetadataType readOnlySpanElementType)
|| TryGetSpanElementType(owningType, isReadOnlySpan: false, out readOnlySpanElementType))
&& ctorSig.Length == 2 && ctorSig[0].IsByRef && ctorSig[1].IsWellKnownType(WellKnownType.Int32))
{
int length = ctorParameters[2].AsInt32();
Expand Down Expand Up @@ -624,6 +625,57 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
}
break;

case ILOpcode.localloc:
{
// Localloc returns an unmanaged pointer to the allocated memory.
// We can't model that in the interpreter memory model. However,
// we can have a narrow path for a common pattern in Span construction:
//
// ldc.i4 X
// localloc
// ldc.i4 X
// newobj instance void valuetype System.Span`1<Y>::.ctor(void*, int32)
StackEntry entry = stack.Pop();
long size = entry.ValueKind switch
{
StackValueKind.Int32 => entry.Value.AsInt32(),
StackValueKind.NativeInt => (context.Target.PointerSize == 4)
? entry.Value.AsInt32() : entry.Value.AsInt64(),
_ => long.MaxValue
};

// Arbitrary limit for allocation size to prevent compiler OOM
if (size < 0 || size > 8192)
return Status.Fail(methodIL.OwningMethod, ILOpcode.localloc);

opcode = reader.ReadILOpcode();
if (opcode < ILOpcode.ldc_i4_0 || opcode > ILOpcode.ldc_i4)
return Status.Fail(methodIL.OwningMethod, ILOpcode.localloc);

int maybeSpanLength = opcode switch
{
ILOpcode.ldc_i4_s => (sbyte)reader.ReadILByte(),
ILOpcode.ldc_i4 => (int)reader.ReadILUInt32(),
_ => opcode - ILOpcode.ldc_i4_0,
};

opcode = reader.ReadILOpcode();
if (opcode != ILOpcode.newobj)
return Status.Fail(methodIL.OwningMethod, ILOpcode.localloc);

var ctorMethod = (MethodDesc)methodIL.GetObject(reader.ReadILToken());
if (!TryGetSpanElementType(ctorMethod.OwningType, isReadOnlySpan: false, out MetadataType elementType)
|| ctorMethod.Signature.Length != 2
|| !ctorMethod.Signature[0].IsPointer
|| !ctorMethod.Signature[1].IsWellKnownType(WellKnownType.Int32)
|| maybeSpanLength * elementType.InstanceFieldSize.AsInt != size)
return Status.Fail(methodIL.OwningMethod, ILOpcode.localloc);

var instance = new ReadOnlySpanValue(elementType, new byte[size], index: 0, (int)size);
stack.PushFromLocation(ctorMethod.OwningType, instance);
}
break;

case ILOpcode.stfld:
{
FieldDesc field = (FieldDesc)methodIL.GetObject(reader.ReadILToken());
Expand Down Expand Up @@ -707,6 +759,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
case ILOpcode.conv_i4:
case ILOpcode.conv_i8:
case ILOpcode.conv_u2:
case ILOpcode.conv_u4:
MichalStrehovsky marked this conversation as resolved.
Show resolved Hide resolved
case ILOpcode.conv_u8:
{
StackEntry popped = stack.Pop();
Expand Down Expand Up @@ -745,6 +798,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
switch (opcode)
{
case ILOpcode.conv_i4:
case ILOpcode.conv_u4:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((int)val));
break;
default:
Expand Down Expand Up @@ -1032,7 +1086,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
StackEntry value2 = stack.Pop();
StackEntry value1 = stack.Pop();

if (value1.ValueKind == StackValueKind.Int32 && value2.ValueKind == StackValueKind.Int32)
if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32)
{
branchTaken = normalizedOpcode switch
{
Expand All @@ -1049,7 +1103,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
_ => throw new NotImplementedException() // unreachable
};
}
else if (value1.ValueKind == StackValueKind.Int64 && value2.ValueKind == StackValueKind.Int64)
else if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64)
{
branchTaken = normalizedOpcode switch
{
Expand Down Expand Up @@ -1136,7 +1190,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
StackEntry value2 = stack.Pop();

bool condition;
if (value1.ValueKind == StackValueKind.Int32 && value2.ValueKind == StackValueKind.Int32)
if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32)
{
if (opcode == ILOpcode.cgt)
condition = value1.Value.AsInt32() < value2.Value.AsInt32();
Expand All @@ -1149,7 +1203,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
else
return Status.Fail(methodIL.OwningMethod, opcode);
}
else if (value1.ValueKind == StackValueKind.Int64 && value2.ValueKind == StackValueKind.Int64)
else if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64)
{
if (opcode == ILOpcode.cgt)
condition = value1.Value.AsInt64() < value2.Value.AsInt64();
Expand Down Expand Up @@ -1238,7 +1292,10 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method

StackEntry value2 = stack.Pop();
StackEntry value1 = stack.Pop();
if (value1.ValueKind == StackValueKind.Int32 && value2.ValueKind == StackValueKind.Int32)

bool isNint = value1.ValueKind == StackValueKind.NativeInt || value2.ValueKind == StackValueKind.NativeInt;

if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32)
{
if (isDivRem && value2.Value.AsInt32() == 0)
return Status.Fail(methodIL.OwningMethod, opcode, "Division by zero");
Expand All @@ -1258,9 +1315,9 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
_ => throw new NotImplementedException(), // unreachable
};

stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(result));
stack.Push(isNint ? StackValueKind.NativeInt : StackValueKind.Int32, ValueTypeValue.FromInt32(result));
}
else if (value1.ValueKind == StackValueKind.Int64 && value2.ValueKind == StackValueKind.Int64)
else if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64)
{
if (isDivRem && value2.Value.AsInt64() == 0)
return Status.Fail(methodIL.OwningMethod, opcode, "Division by zero");
Expand All @@ -1279,7 +1336,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
_ => throw new NotImplementedException(), // unreachable
};

stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64(result));
stack.Push(isNint ? StackValueKind.NativeInt : StackValueKind.Int64, ValueTypeValue.FromInt64(result));
}
else if (value1.ValueKind == StackValueKind.Float && value2.ValueKind == StackValueKind.Float)
{
Expand All @@ -1305,7 +1362,32 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
&& opcode == ILOpcode.shl)
{
long result = value1.Value.AsInt64() << value2.Value.AsInt32();
stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64(result));
stack.Push(isNint ? StackValueKind.NativeInt : StackValueKind.Int64, ValueTypeValue.FromInt64(result));
}
else if ((value1.ValueKind == StackValueKind.ByRef && value2.ValueKind != StackValueKind.ByRef)
|| (value2.ValueKind == StackValueKind.ByRef && value1.ValueKind != StackValueKind.ByRef))
{
if (opcode != ILOpcode.add)
ThrowHelper.ThrowInvalidProgramException();

StackEntry reference = value1.ValueKind == StackValueKind.ByRef ? value1 : value2;
StackEntry addend = value1.ValueKind != StackValueKind.ByRef ? value1 : value2;

if (addend.ValueKind is not StackValueKind.NativeInt and not StackValueKind.Int32)
ThrowHelper.ThrowInvalidProgramException();

long addition = addend.ValueKind switch
{
StackValueKind.Int32 => addend.Value.AsInt32(),
_ => context.Target.PointerSize == 8 ? addend.Value.AsInt64() : addend.Value.AsInt32()
};

var previousByRef = (ByRefValue)reference.Value;
if (addition > previousByRef.PointedToBytes.Length - previousByRef.PointedToOffset
|| addition + previousByRef.PointedToOffset < 0)
return Status.Fail(methodIL.OwningMethod, "Out of range byref access");

stack.Push(StackValueKind.ByRef, new ByRefValue(previousByRef.PointedToBytes, (int)(previousByRef.PointedToOffset + addition)));
}
else
{
Expand Down Expand Up @@ -1599,6 +1681,32 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
constrainedType = methodIL.GetObject(reader.ReadILToken()) as TypeDesc;
goto again;

case ILOpcode.unaligned:
reader.ReadILByte();
break;

case ILOpcode.initblk:
{
StackEntry size = stack.Pop();
StackEntry value = stack.Pop();
StackEntry addr = stack.Pop();

if (size.ValueKind != StackValueKind.Int32
|| value.ValueKind != StackValueKind.Int32
|| addr.ValueKind != StackValueKind.ByRef)
return Status.Fail(methodIL.OwningMethod, opcode);

uint sizeBytes = (uint)size.Value.AsInt32();

var addressValue = (ByRefValue)addr.Value;
if (sizeBytes > addressValue.PointedToBytes.Length - addressValue.PointedToOffset
|| sizeBytes > int.MaxValue /* paranoid check that cast to int is legit */)
return Status.Fail(methodIL.OwningMethod, opcode);

Array.Fill(addressValue.PointedToBytes, (byte)value.Value.AsInt32(), addressValue.PointedToOffset, (int)sizeBytes);
}
break;

default:
return Status.Fail(methodIL.OwningMethod, opcode);
}
Expand All @@ -1608,14 +1716,14 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
return Status.Fail(methodIL.OwningMethod, "Control fell through");
}

private static bool TryGetReadOnlySpanElementType(TypeDesc type, out MetadataType elementType)
private static bool TryGetSpanElementType(TypeDesc type, bool isReadOnlySpan, out MetadataType elementType)
{
if (type.IsByRefLike
&& type is MetadataType maybeReadOnlySpan
&& maybeReadOnlySpan.Module == type.Context.SystemModule
&& maybeReadOnlySpan.Name == "ReadOnlySpan`1"
&& maybeReadOnlySpan.Namespace == "System"
&& maybeReadOnlySpan.Instantiation[0] is MetadataType readOnlySpanElementType)
&& type is MetadataType maybeSpan
&& maybeSpan.Module == type.Context.SystemModule
&& ((isReadOnlySpan && maybeSpan.Name == "ReadOnlySpan`1") || (!isReadOnlySpan && maybeSpan.Name == "Span`1"))
&& maybeSpan.Namespace == "System"
&& maybeSpan.Instantiation[0] is MetadataType readOnlySpanElementType)
{
elementType = readOnlySpanElementType;
return true;
Expand All @@ -1630,10 +1738,14 @@ private static BaseValueTypeValue NewUninitializedLocationValue(TypeDesc locatio
{
return null;
}
else if (TryGetReadOnlySpanElementType(locationType, out MetadataType readOnlySpanElementType))
else if (TryGetSpanElementType(locationType, isReadOnlySpan: true, out MetadataType readOnlySpanElementType))
{
return new ReadOnlySpanValue(readOnlySpanElementType, Array.Empty<byte>(), 0, 0);
}
else if (TryGetSpanElementType(locationType, isReadOnlySpan: false, out MetadataType spanElementType))
{
return new ReadOnlySpanValue(spanElementType, Array.Empty<byte>(), 0, 0);
}
else
{
Debug.Assert(locationType.IsValueType || locationType.IsPointer || locationType.IsFunctionPointer);
Expand Down Expand Up @@ -1948,18 +2060,6 @@ public Value PopIntoLocation(TypeDesc locationType)
}
}

private enum StackValueKind
{
Unknown,
Int32,
Int64,
NativeInt,
Float,
ByRef,
ObjRef,
ValueType,
}

/// <summary>
/// Represents a field value that can be serialized into a preinitialized blob.
/// </summary>
Expand Down Expand Up @@ -2340,10 +2440,19 @@ public bool TryAccessElement(int index, out Value value)

public Value GetField(FieldDesc field)
{
if (field.Name != "_length")
MetadataType elementType;
if (!TryGetSpanElementType(field.OwningType, isReadOnlySpan: true, out elementType)
&& !TryGetSpanElementType(field.OwningType, isReadOnlySpan: false, out elementType))
ThrowHelper.ThrowInvalidProgramException();

return ValueTypeValue.FromInt32(_length / _elementType.InstanceFieldSize.AsInt);
if (elementType != _elementType)
ThrowHelper.ThrowInvalidProgramException();

if (field.Name == "_length")
return ValueTypeValue.FromInt32(_length / _elementType.InstanceFieldSize.AsInt);

Debug.Assert(field.Name == "_reference");
return new ByRefValue(_bytes, _index);
}

public ByRefValue GetFieldAddress(FieldDesc field)
Expand Down Expand Up @@ -3016,4 +3125,15 @@ public sealed class TypeLoaderAwarePreinitializationPolicy : TypePreinitializati
public override bool CanPreinitializeAllConcreteFormsForCanonForm(DefType type) => false;
}
}

#pragma warning disable SA1400 // Element 'Extensions' should declare an access modifier
file static class Extensions
{
public static StackValueKind WithNormalizedNativeInt(this StackValueKind kind, TypeSystemContext context)
=> kind switch
{
StackValueKind.NativeInt => context.Target.PointerSize == 8 ? StackValueKind.Int64 : StackValueKind.Int32,
_ => kind
};
}
}
Loading