in src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs [124:1828]
private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<MethodDesc> recursionProtect, ref int instructionCounter, out Value returnValue)
{
returnValue = default;
if (recursionProtect != null && recursionProtect.Contains(methodIL.OwningMethod))
return Status.Fail(methodIL.OwningMethod, "Recursion");
ILExceptionRegion[] ehRegions = methodIL.GetExceptionRegions();
if (ehRegions != null && ehRegions.Length > 0)
{
// We don't care about catch/filter/fault because those only run when an exception happens
// (exceptions will never happen here). But finally needs to run in non-exceptional paths
// and we don't model that yet.
foreach (ILExceptionRegion ehRegion in ehRegions)
{
if (ehRegion.Kind == ILExceptionRegionKind.Finally)
return Status.Fail(methodIL.OwningMethod, "Finally regions");
}
}
var reader = new ILReader(methodIL.GetILBytes());
TypeSystemContext context = methodIL.OwningMethod.Context;
var stack = new Stack(methodIL.MaxStack, context.Target);
LocalVariableDefinition[] localTypes = methodIL.GetLocals();
Value[] locals = new Value[localTypes.Length];
for (int i = 0; i < localTypes.Length; i++)
{
locals[i] = NewUninitializedLocationValue(localTypes[i].Type);
}
// Read IL opcodes and interpret their semantics.
//
// This is not a full interpreter and we're allowed to not interpret everything. If a semantic is
// not implemented by the interpreter, we simply fail.
//
// We also need to do basic sanity checking for invalid IL to protect us from crashing. These
// all throw the TypeSystem's InvalidProgramException. The exception doesn't need to exactly match
// the runtime exception. We just need something reasonably catchable to abort interpreting.
//
// We throw instead of returning false to aid debuggability of the interpreter (we shouldn't see
// exceptions in normal code so an exception is usually a bug).
while (reader.HasNext)
{
if (instructionCounter == 100000)
return Status.Fail(methodIL.OwningMethod, "Instruction limit");
instructionCounter++;
TypeDesc constrainedType = null;
again:
ILOpcode opcode = reader.ReadILOpcode();
switch (opcode)
{
case ILOpcode.ldc_i4_m1:
case ILOpcode.ldc_i4_s:
case ILOpcode.ldc_i4:
case ILOpcode.ldc_i4_0:
case ILOpcode.ldc_i4_1:
case ILOpcode.ldc_i4_2:
case ILOpcode.ldc_i4_3:
case ILOpcode.ldc_i4_4:
case ILOpcode.ldc_i4_5:
case ILOpcode.ldc_i4_6:
case ILOpcode.ldc_i4_7:
case ILOpcode.ldc_i4_8:
{
int value = opcode switch
{
ILOpcode.ldc_i4_m1 => -1,
ILOpcode.ldc_i4_s => (sbyte)reader.ReadILByte(),
ILOpcode.ldc_i4 => (int)reader.ReadILUInt32(),
_ => opcode - ILOpcode.ldc_i4_0,
};
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(value));
}
break;
case ILOpcode.ldc_i8:
stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64((long)reader.ReadILUInt64()));
break;
case ILOpcode.ldc_r4:
case ILOpcode.ldc_r8:
stack.Push(StackValueKind.Float, ValueTypeValue.FromDouble(
opcode == ILOpcode.ldc_r4 ? reader.ReadILFloat() : reader.ReadILDouble()));
break;
case ILOpcode.sizeof_:
{
TypeDesc type = (TypeDesc)methodIL.GetObject(reader.ReadILToken());
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(type.GetElementSize().AsInt));
}
break;
case ILOpcode.ldnull:
stack.Push((ReferenceTypeValue)null);
break;
case ILOpcode.newarr:
{
if (!stack.TryPopIntValue(out int elementCount))
{
ThrowHelper.ThrowInvalidProgramException();
}
const int MaximumInterpretedArraySize = 8192;
TypeDesc elementType = (TypeDesc)methodIL.GetObject(reader.ReadILToken());
if (elementCount > 0
&& (elementType.IsGCPointer
|| (elementType.IsValueType && ((DefType)elementType).ContainsGCPointers)))
{
return Status.Fail(methodIL.OwningMethod, opcode, "GC pointers");
}
if (elementCount < 0
|| elementCount > MaximumInterpretedArraySize)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Array out of bounds");
}
if (elementType.RequiresAlign8())
{
return Status.Fail(methodIL.OwningMethod, opcode, "Align8");
}
AllocationSite allocSite = new AllocationSite(_type, instructionCounter);
stack.Push(new ArrayInstance(elementType.MakeArrayType(), elementCount, allocSite));
}
break;
case ILOpcode.dup:
if (stack.Count == 0)
{
ThrowHelper.ThrowInvalidProgramException();
}
stack.Push(stack.Peek());
break;
case ILOpcode.pop:
{
stack.Pop();
break;
}
case ILOpcode.ldstr:
{
string s = (string)methodIL.GetObject(reader.ReadILToken());
if (!_internedStrings.TryGetValue(s, out StringInstance instance))
{
instance = new StringInstance(context.GetWellKnownType(WellKnownType.String), s);
_internedStrings.Add(s, instance);
}
stack.Push(instance);
}
break;
case ILOpcode.ret:
{
bool returnsVoid = methodIL.OwningMethod.Signature.ReturnType.IsVoid;
if ((returnsVoid && stack.Count > 0)
|| (!returnsVoid && stack.Count != 1))
{
ThrowHelper.ThrowInvalidProgramException();
}
if (!returnsVoid)
{
returnValue = stack.PopIntoLocation(methodIL.OwningMethod.Signature.ReturnType);
}
return Status.Success;
}
case ILOpcode.nop:
case ILOpcode.volatile_:
break;
case ILOpcode.stsfld:
{
FieldDesc field = (FieldDesc)methodIL.GetObject(reader.ReadILToken());
if (!field.IsStatic || field.IsLiteral)
{
ThrowHelper.ThrowInvalidProgramException();
}
if (field.OwningType != _type)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Store into other static");
}
if (field.IsThreadStatic || field.HasRva)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Unsupported static");
}
if (_flowAnnotations.RequiresDataflowAnalysisDueToSignature(field))
{
return Status.Fail(methodIL.OwningMethod, opcode, "Needs dataflow analysis");
}
if (_fieldValues[field] is IAssignableValue assignableField)
{
if (!assignableField.TryAssign(stack.PopIntoLocation(field.FieldType)))
{
return Status.Fail(methodIL.OwningMethod, opcode, "Unsupported store");
}
}
else
{
Value value = stack.PopIntoLocation(field.FieldType);
if (value is IInternalModelingOnlyValue)
return Status.Fail(methodIL.OwningMethod, opcode, "Value with no external representation");
_fieldValues[field] = value;
}
}
break;
case ILOpcode.ldsfld:
{
FieldDesc field = (FieldDesc)methodIL.GetObject(reader.ReadILToken());
if (!field.IsStatic || field.IsLiteral)
{
ThrowHelper.ThrowInvalidProgramException();
}
if (field.IsThreadStatic || field.HasRva)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Unsupported static");
}
if (field.OwningType == _type)
{
stack.PushFromLocation(field.FieldType, _fieldValues[field]);
}
else if (_readOnlyPolicy.IsReadOnly(field)
&& field.OwningType.HasStaticConstructor
&& _policy.CanPreinitialize(field.OwningType))
{
TypePreinit nestedPreinit = new TypePreinit((MetadataType)field.OwningType, _compilationGroup, _ilProvider, _policy, _readOnlyPolicy, _flowAnnotations);
recursionProtect ??= new Stack<MethodDesc>();
recursionProtect.Push(methodIL.OwningMethod);
// Since we don't reset the instruction counter as we interpret the nested cctor,
// remember the instruction counter before we start interpreting so that we can subtract
// the instructions later when we convert object instances allocated in the nested
// cctor to foreign instances in the currently analyzed cctor.
// E.g. if the nested cctor allocates a new object at the beginning of the cctor,
// we should treat it as a ForeignTypeInstance with allocation site ID 0, not allocation
// site ID of `instructionCounter + 0`.
// We could also reset the counter, but we use the instruction counter as a complexity cutoff
// and resetting it would lead to unpredictable analysis durations.
int baseInstructionCounter = instructionCounter;
Status status = nestedPreinit.TryScanMethod(field.OwningType.GetStaticConstructor(), null, recursionProtect, ref instructionCounter, out Value _);
if (!status.IsSuccessful)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Nested cctor failed to preinit");
}
recursionProtect.Pop();
Value value = nestedPreinit._fieldValues[field];
if (value is ValueTypeValue)
stack.PushFromLocation(field.FieldType, value);
else if (value is ReferenceTypeValue referenceType)
stack.PushFromLocation(field.FieldType, referenceType.ToForeignInstance(baseInstructionCounter, this));
else
return Status.Fail(methodIL.OwningMethod, opcode);
}
else if (_readOnlyPolicy.IsReadOnly(field)
&& !field.OwningType.HasStaticConstructor)
{
// (Effectively) read only field but no static constructor to set it: the value is default-initialized.
stack.PushFromLocation(field.FieldType, NewUninitializedLocationValue(field.FieldType));
}
else
{
return Status.Fail(methodIL.OwningMethod, opcode, "Load from other non-initonly static");
}
}
break;
case ILOpcode.ldsflda:
{
FieldDesc field = (FieldDesc)methodIL.GetObject(reader.ReadILToken());
if (!field.IsStatic || field.IsLiteral)
{
ThrowHelper.ThrowInvalidProgramException();
}
if (field.OwningType != _type)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Address of other static");
}
if (field.IsThreadStatic || field.HasRva)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Unsupported static");
}
if (_flowAnnotations.RequiresDataflowAnalysisDueToSignature(field))
{
return Status.Fail(methodIL.OwningMethod, opcode, "Needs dataflow analysis");
}
Value fieldValue = _fieldValues[field];
if (fieldValue == null || !fieldValue.TryCreateByRef(out Value byRefValue))
{
return Status.Fail(methodIL.OwningMethod, opcode, "Unsupported byref");
}
stack.Push(StackValueKind.ByRef, byRefValue);
}
break;
case ILOpcode.call:
case ILOpcode.callvirt:
{
MethodDesc method = (MethodDesc)methodIL.GetObject(reader.ReadILToken());
MethodSignature methodSig = method.Signature;
int paramOffset = methodSig.IsStatic ? 0 : 1;
int numParams = methodSig.Length + paramOffset;
if (constrainedType != null)
{
DefaultInterfaceMethodResolution staticResolution = default;
MethodDesc directMethod = constrainedType.GetClosestDefType().TryResolveConstraintMethodApprox(method.OwningType, method, out bool forceUseRuntimeLookup, ref staticResolution);
if (directMethod == null || forceUseRuntimeLookup)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Did not resolve constraint");
}
method = directMethod;
}
TypeDesc owningType = method.OwningType;
if (!_compilationGroup.CanInline(methodIL.OwningMethod, method))
{
return Status.Fail(methodIL.OwningMethod, opcode, "Cannot inline");
}
if (owningType.HasStaticConstructor
&& owningType != methodIL.OwningMethod.OwningType
&& !((MetadataType)owningType).IsBeforeFieldInit)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Static constructor");
}
if (_flowAnnotations.RequiresDataflowAnalysisDueToSignature(method))
{
return Status.Fail(methodIL.OwningMethod, opcode, "Needs dataflow analysis");
}
Value[] methodParams = new Value[numParams];
for (int i = numParams - 1; i >= 0; i--)
{
methodParams[i] = stack.PopIntoLocation(GetArgType(method, i));
}
if (opcode == ILOpcode.callvirt)
{
// Only support non-virtual methods for now + we don't emulate NRE on null this
if (!owningType.IsValueType && (method.IsVirtual || methodParams[0] == null))
return Status.Fail(methodIL.OwningMethod, opcode);
}
Value retVal;
if (!method.IsIntrinsic || !TryHandleIntrinsicCall(method, methodParams, out retVal))
{
recursionProtect ??= new Stack<MethodDesc>();
recursionProtect.Push(methodIL.OwningMethod);
Status callResult = TryScanMethod(method, methodParams, recursionProtect, ref instructionCounter, out retVal);
if (!callResult.IsSuccessful)
{
recursionProtect.Pop();
return callResult;
}
recursionProtect.Pop();
}
if (!methodSig.ReturnType.IsVoid)
stack.PushFromLocation(methodSig.ReturnType, retVal);
}
break;
case ILOpcode.newobj:
{
MethodDesc ctor = (MethodDesc)methodIL.GetObject(reader.ReadILToken());
MethodSignature ctorSig = ctor.Signature;
TypeDesc owningType = ctor.OwningType;
if (!_compilationGroup.CanInline(methodIL.OwningMethod, ctor)
|| !_compilationGroup.ContainsType(owningType))
{
return Status.Fail(methodIL.OwningMethod, opcode, "Cannot inline");
}
if (owningType.HasStaticConstructor
&& owningType != methodIL.OwningMethod.OwningType
&& !((MetadataType)owningType).IsBeforeFieldInit)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Static constructor");
}
if (!owningType.IsDefType)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Not a class or struct");
}
if (owningType.HasFinalizer)
{
// We have a finalizer. There's still a small chance it has been nopped out
// with a feature switch. Check for that.
byte[] finalizerMethodILBytes = _ilProvider.GetMethodIL(owningType.GetFinalizer()).GetILBytes();
if (finalizerMethodILBytes.Length != 1 || finalizerMethodILBytes[0] != (byte)ILOpcode.ret)
{
// Finalizer might have observable side effects
return Status.Fail(methodIL.OwningMethod, opcode, "Finalizable class");
}
}
if (_flowAnnotations.RequiresDataflowAnalysisDueToSignature(ctor))
{
return Status.Fail(methodIL.OwningMethod, opcode, "Needs dataflow analysis");
}
if (owningType.RequiresAlign8())
{
return Status.Fail(methodIL.OwningMethod, opcode, "Align8");
}
Value[] ctorParameters = new Value[ctorSig.Length + 1];
for (int i = ctorSig.Length - 1; i >= 0; i--)
{
ctorParameters[i + 1] = stack.PopIntoLocation(GetArgType(ctor, i + 1));
}
AllocationSite allocSite = new AllocationSite(_type, instructionCounter);
Value instance;
if (owningType.IsDelegate)
{
if (!(ctorParameters[2] is MethodPointerValue methodPointer))
{
return Status.Fail(methodIL.OwningMethod, opcode, "Unverifiable delegate creation");
}
ReferenceTypeValue firstParameter = null;
if (ctorParameters[1] != null)
{
firstParameter = ctorParameters[1] as ReferenceTypeValue;
if (firstParameter == null)
{
ThrowHelper.ThrowInvalidProgramException();
}
}
MethodDesc pointedMethod = methodPointer.PointedToMethod;
if ((firstParameter == null) != pointedMethod.Signature.IsStatic)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Open/closed static/instance delegate mismatch");
}
if (firstParameter != null && pointedMethod.HasInstantiation)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Delegate with fat pointer");
}
instance = new DelegateInstance(owningType, pointedMethod, firstParameter, allocSite);
}
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();
if (ctorParameters[1] is not ByRefValue byref)
{
ThrowHelper.ThrowInvalidProgramException();
return default; // unreached
}
byte[] bytes = byref.PointedToBytes;
int byteOffset = byref.PointedToOffset;
int byteLength = length * readOnlySpanElementType.InstanceFieldSize.AsInt;
if (bytes.Length - byteOffset < byteLength)
return Status.Fail(ctor, "Out of range memory access");
instance = new ReadOnlySpanValue(readOnlySpanElementType, bytes, byteOffset, byteLength);
}
else
{
if (owningType.IsValueType)
{
instance = new ValueTypeValue(owningType);
bool byrefCreated = instance.TryCreateByRef(out ctorParameters[0]);
Debug.Assert(byrefCreated);
}
else
{
instance = new ObjectInstance((DefType)owningType, allocSite);
ctorParameters[0] = instance;
}
if (((DefType)owningType).ContainsGCPointers)
{
// We don't want to end up with GC pointers in the frozen region
// because write barriers can't handle that.
// We can make an exception for readonly fields.
bool allGcPointersAreReadonly = true;
TypeDesc currentType = owningType;
do
{
foreach (FieldDesc field in currentType.GetFields())
{
if (field.IsStatic)
continue;
TypeDesc fieldType = field.FieldType;
if (fieldType.IsGCPointer)
{
if (!_readOnlyPolicy.IsReadOnly(field))
{
allGcPointersAreReadonly = false;
break;
}
}
else if (fieldType.IsValueType && ((DefType)fieldType).ContainsGCPointers)
{
allGcPointersAreReadonly = false;
break;
}
}
} while (allGcPointersAreReadonly && (currentType = currentType.BaseType) != null && !currentType.IsValueType);
if (!allGcPointersAreReadonly)
return Status.Fail(methodIL.OwningMethod, opcode, "GC pointers");
}
recursionProtect ??= new Stack<MethodDesc>();
recursionProtect.Push(methodIL.OwningMethod);
Status ctorCallResult = TryScanMethod(ctor, ctorParameters, recursionProtect, ref instructionCounter, out _);
if (!ctorCallResult.IsSuccessful)
{
recursionProtect.Pop();
return ctorCallResult;
}
recursionProtect.Pop();
}
stack.PushFromLocation(owningType, instance);
}
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());
if (field.IsStatic)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Static field with stfld");
}
Value value = stack.PopIntoLocation(field.FieldType);
StackEntry instance = stack.Pop();
if (field.FieldType.IsGCPointer && value != null)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Reference field");
}
if (field.FieldType.IsByRef)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Byref field");
}
if (_flowAnnotations.RequiresDataflowAnalysisDueToSignature(field))
{
return Status.Fail(methodIL.OwningMethod, opcode, "Needs dataflow analysis");
}
if (instance.Value is not IHasInstanceFields settableInstance
|| !settableInstance.TrySetField(field, value))
{
return Status.Fail(methodIL.OwningMethod, opcode, "Not settable");
}
}
break;
case ILOpcode.ldfld:
{
FieldDesc field = (FieldDesc)methodIL.GetObject(reader.ReadILToken());
if (field.FieldType.IsGCPointer
|| field.IsStatic)
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
StackEntry instance = stack.Pop();
var loadableInstance = instance.Value as IHasInstanceFields;
if (loadableInstance == null)
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
Value fieldValue = loadableInstance.GetField(field);
stack.PushFromLocation(field.FieldType, fieldValue);
}
break;
case ILOpcode.ldflda:
{
FieldDesc field = (FieldDesc)methodIL.GetObject(reader.ReadILToken());
if (field.FieldType.IsGCPointer
|| field.IsStatic)
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
if (_flowAnnotations.RequiresDataflowAnalysisDueToSignature(field))
{
return Status.Fail(methodIL.OwningMethod, opcode, "Needs dataflow analysis");
}
StackEntry instance = stack.Pop();
var loadableInstance = instance.Value as IHasInstanceFields;
if (loadableInstance == null)
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
stack.Push(StackValueKind.ByRef, loadableInstance.GetFieldAddress(field));
}
break;
case ILOpcode.conv_i:
case ILOpcode.conv_u:
case ILOpcode.conv_i1:
case ILOpcode.conv_i2:
case ILOpcode.conv_i4:
case ILOpcode.conv_i8:
case ILOpcode.conv_u1:
case ILOpcode.conv_u2:
case ILOpcode.conv_u4:
case ILOpcode.conv_u8:
{
StackEntry popped = stack.Pop();
if (popped.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32)
{
int val = popped.Value.AsInt32();
switch (opcode)
{
case ILOpcode.conv_i:
stack.Push(StackValueKind.NativeInt,
context.Target.PointerSize == 8 ? ValueTypeValue.FromInt64(val) : ValueTypeValue.FromInt32(val));
break;
case ILOpcode.conv_u:
stack.Push(StackValueKind.NativeInt,
context.Target.PointerSize == 8 ? ValueTypeValue.FromInt64((uint)val) : ValueTypeValue.FromInt32(val));
break;
case ILOpcode.conv_i1:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((sbyte)val));
break;
case ILOpcode.conv_i2:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((short)val));
break;
case ILOpcode.conv_i4:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(val));
break;
case ILOpcode.conv_i8:
stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64(val));
break;
case ILOpcode.conv_u1:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((byte)val));
break;
case ILOpcode.conv_u2:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((ushort)val));
break;
case ILOpcode.conv_u4:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(val));
break;
case ILOpcode.conv_u8:
stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64((uint)val));
break;
default:
return Status.Fail(methodIL.OwningMethod, opcode);
}
}
else if (popped.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64)
{
long val = popped.Value.AsInt64();
switch (opcode)
{
case ILOpcode.conv_u:
case ILOpcode.conv_i:
stack.Push(StackValueKind.NativeInt,
context.Target.PointerSize == 8 ? ValueTypeValue.FromInt64(val) : ValueTypeValue.FromInt32((int)val));
break;
case ILOpcode.conv_i1:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((sbyte)val));
break;
case ILOpcode.conv_i2:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((short)val));
break;
case ILOpcode.conv_i4:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((int)val));
break;
case ILOpcode.conv_i8:
stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64(val));
break;
case ILOpcode.conv_u1:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((byte)val));
break;
case ILOpcode.conv_u2:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((ushort)val));
break;
case ILOpcode.conv_u4:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32((int)val));
break;
case ILOpcode.conv_u8:
stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64(val));
break;
default:
return Status.Fail(methodIL.OwningMethod, opcode);
}
}
else if (popped.ValueKind == StackValueKind.Float)
{
double val = popped.Value.AsDouble();
switch (opcode)
{
case ILOpcode.conv_i8:
stack.Push(StackValueKind.Int64, ValueTypeValue.FromInt64((long)val));
break;
default:
return Status.Fail(methodIL.OwningMethod, opcode);
}
}
else if (popped.ValueKind == StackValueKind.ByRef
&& (opcode == ILOpcode.conv_i || opcode == ILOpcode.conv_u)
&& (reader.PeekILOpcode() is (>= 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.
// Roslyn however sometimes emits a sequence of conv_u followed by ldind and we can
// have a narrow path to handle that one.
//
// For example:
//
// static unsafe U Read<T, U>(T val) where T : unmanaged where U : unmanaged => *(U*)&val;
stack.Push(popped);
goto again;
}
else
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
}
break;
case ILOpcode.ldarg_0:
case ILOpcode.ldarg_1:
case ILOpcode.ldarg_2:
case ILOpcode.ldarg_3:
case ILOpcode.ldarg_s:
case ILOpcode.ldarg:
{
int index = opcode switch
{
ILOpcode.ldarg_s => reader.ReadILByte(),
ILOpcode.ldarg => reader.ReadILUInt16(),
_ => opcode - ILOpcode.ldarg_0,
};
stack.PushFromLocation(GetArgType(methodIL.OwningMethod, index), parameters[index]);
}
break;
case ILOpcode.starg_s:
case ILOpcode.starg:
{
int index = opcode == ILOpcode.starg ? reader.ReadILUInt16() : reader.ReadILByte();
TypeDesc argType = GetArgType(methodIL.OwningMethod, index);
if (parameters[index] is IAssignableValue assignableParam)
{
if (!assignableParam.TryAssign(stack.PopIntoLocation(argType)))
{
return Status.Fail(methodIL.OwningMethod, opcode, "Unsupported store");
}
}
else
parameters[index] = stack.PopIntoLocation(argType);
}
break;
case ILOpcode.ldtoken:
{
var token = methodIL.GetObject(reader.ReadILToken());
if (token is FieldDesc field)
{
stack.Push(new StackEntry(StackValueKind.ValueType, new RuntimeFieldHandleValue(field)));
}
else if (token is TypeDesc type)
{
stack.Push(new StackEntry(StackValueKind.ValueType, new RuntimeTypeHandleValue(type)));
}
else
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
}
break;
case ILOpcode.ldftn:
{
if (constrainedType != null)
return Status.Fail(methodIL.OwningMethod, ILOpcode.constrained);
var method = methodIL.GetObject(reader.ReadILToken()) as MethodDesc;
if (method != null)
stack.Push(StackValueKind.NativeInt, new MethodPointerValue(method));
else
ThrowHelper.ThrowInvalidProgramException();
}
break;
case ILOpcode.ldloc_0:
case ILOpcode.ldloc_1:
case ILOpcode.ldloc_2:
case ILOpcode.ldloc_3:
case ILOpcode.ldloc_s:
case ILOpcode.ldloc:
{
int index = opcode switch
{
ILOpcode.ldloc_s => reader.ReadILByte(),
ILOpcode.ldloc => reader.ReadILUInt16(),
_ => opcode - ILOpcode.ldloc_0,
};
if (index >= locals.Length)
{
ThrowHelper.ThrowInvalidProgramException();
}
stack.PushFromLocation(localTypes[index].Type, locals[index]);
}
break;
case ILOpcode.stloc_0:
case ILOpcode.stloc_1:
case ILOpcode.stloc_2:
case ILOpcode.stloc_3:
case ILOpcode.stloc_s:
case ILOpcode.stloc:
{
int index = opcode switch
{
ILOpcode.stloc_s => reader.ReadILByte(),
ILOpcode.stloc => reader.ReadILUInt16(),
_ => opcode - ILOpcode.stloc_0,
};
if (index >= locals.Length)
{
ThrowHelper.ThrowInvalidProgramException();
}
TypeDesc localType = localTypes[index].Type;
if (locals[index] is IAssignableValue assignableLocal)
{
if (!assignableLocal.TryAssign(stack.PopIntoLocation(localType)))
{
return Status.Fail(methodIL.OwningMethod, opcode, "Unsupported store");
}
}
else
locals[index] = stack.PopIntoLocation(localType);
}
break;
case ILOpcode.ldarga_s:
case ILOpcode.ldarga:
case ILOpcode.ldloca_s:
case ILOpcode.ldloca:
{
int index = opcode switch
{
ILOpcode.ldloca_s or ILOpcode.ldarga_s => reader.ReadILByte(),
ILOpcode.ldloca or ILOpcode.ldarga => reader.ReadILUInt16(),
_ => throw new NotImplementedException(), // Unreachable
};
Value[] storage = opcode is ILOpcode.ldloca or ILOpcode.ldloca_s ? locals : parameters;
if (index >= storage.Length)
{
ThrowHelper.ThrowInvalidProgramException();
}
Value localValue = storage[index];
if (localValue == null || !localValue.TryCreateByRef(out Value byrefValue))
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
else
{
stack.Push(StackValueKind.ByRef, byrefValue);
}
}
break;
case ILOpcode.initobj:
{
StackEntry popped = stack.Pop();
if (popped.ValueKind != StackValueKind.ByRef)
{
ThrowHelper.ThrowInvalidProgramException();
}
TypeDesc token = (TypeDesc)methodIL.GetObject(reader.ReadILToken());
if (token.IsGCPointer || popped.Value is not ByRefValue byrefVal)
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
byrefVal.Initialize(token.GetElementSize().AsInt);
}
break;
case ILOpcode.br:
case ILOpcode.brfalse:
case ILOpcode.brtrue:
case ILOpcode.blt:
case ILOpcode.blt_un:
case ILOpcode.bgt:
case ILOpcode.bgt_un:
case ILOpcode.beq:
case ILOpcode.bne_un:
case ILOpcode.bge:
case ILOpcode.bge_un:
case ILOpcode.ble:
case ILOpcode.ble_un:
case ILOpcode.br_s:
case ILOpcode.brfalse_s:
case ILOpcode.brtrue_s:
case ILOpcode.blt_s:
case ILOpcode.blt_un_s:
case ILOpcode.bgt_s:
case ILOpcode.bgt_un_s:
case ILOpcode.beq_s:
case ILOpcode.bne_un_s:
case ILOpcode.bge_s:
case ILOpcode.bge_un_s:
case ILOpcode.ble_s:
case ILOpcode.ble_un_s:
{
int delta = opcode >= ILOpcode.br ?
(int)reader.ReadILUInt32() :
(sbyte)reader.ReadILByte();
int target = reader.Offset + delta;
if (target < 0
|| target > reader.Size)
{
ThrowHelper.ThrowInvalidProgramException();
}
ILOpcode normalizedOpcode = opcode >= ILOpcode.br ?
opcode - ILOpcode.br + ILOpcode.br_s:
opcode;
bool branchTaken;
if (normalizedOpcode == ILOpcode.brtrue_s || normalizedOpcode == ILOpcode.brfalse_s)
{
StackEntry condition = stack.Pop();
if (condition.ValueKind == StackValueKind.Int32 || (condition.ValueKind == StackValueKind.NativeInt && context.Target.PointerSize == 4))
branchTaken = normalizedOpcode == ILOpcode.brfalse_s
? condition.Value.AsInt32() == 0 : condition.Value.AsInt32() != 0;
else if (condition.ValueKind == StackValueKind.Int64 || (condition.ValueKind == StackValueKind.NativeInt && context.Target.PointerSize == 8))
branchTaken = normalizedOpcode == ILOpcode.brfalse_s
? condition.Value.AsInt64() == 0 : condition.Value.AsInt64() != 0;
else if (condition.ValueKind == StackValueKind.ObjRef)
branchTaken = normalizedOpcode == ILOpcode.brfalse_s
? condition.Value == null : condition.Value != null;
else
return Status.Fail(methodIL.OwningMethod, opcode);
}
else if (normalizedOpcode == ILOpcode.blt_s || normalizedOpcode == ILOpcode.bgt_s
|| normalizedOpcode == ILOpcode.bge_s || normalizedOpcode == ILOpcode.beq_s
|| normalizedOpcode == ILOpcode.ble_s || normalizedOpcode == ILOpcode.blt_un_s
|| normalizedOpcode == ILOpcode.ble_un_s || normalizedOpcode == ILOpcode.bge_un_s
|| normalizedOpcode == ILOpcode.bgt_un_s || normalizedOpcode == ILOpcode.bne_un_s)
{
StackEntry value2 = stack.Pop();
StackEntry value1 = stack.Pop();
if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32)
{
branchTaken = normalizedOpcode switch
{
ILOpcode.blt_s => value1.Value.AsInt32() < value2.Value.AsInt32(),
ILOpcode.blt_un_s => (uint)value1.Value.AsInt32() < (uint)value2.Value.AsInt32(),
ILOpcode.bgt_s => value1.Value.AsInt32() > value2.Value.AsInt32(),
ILOpcode.bgt_un_s => (uint)value1.Value.AsInt32() > (uint)value2.Value.AsInt32(),
ILOpcode.bge_s => value1.Value.AsInt32() >= value2.Value.AsInt32(),
ILOpcode.bge_un_s => (uint)value1.Value.AsInt32() >= (uint)value2.Value.AsInt32(),
ILOpcode.beq_s => value1.Value.AsInt32() == value2.Value.AsInt32(),
ILOpcode.bne_un_s => value1.Value.AsInt32() != value2.Value.AsInt32(),
ILOpcode.ble_s => value1.Value.AsInt32() <= value2.Value.AsInt32(),
ILOpcode.ble_un_s => (uint)value1.Value.AsInt32() <= (uint)value2.Value.AsInt32(),
_ => throw new NotImplementedException() // unreachable
};
}
else if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64)
{
branchTaken = normalizedOpcode switch
{
ILOpcode.blt_s => value1.Value.AsInt64() < value2.Value.AsInt64(),
ILOpcode.blt_un_s => (ulong)value1.Value.AsInt64() < (ulong)value2.Value.AsInt64(),
ILOpcode.bgt_s => value1.Value.AsInt64() > value2.Value.AsInt64(),
ILOpcode.bgt_un_s => (ulong)value1.Value.AsInt64() > (ulong)value2.Value.AsInt64(),
ILOpcode.bge_s => value1.Value.AsInt64() >= value2.Value.AsInt64(),
ILOpcode.bge_un_s => (ulong)value1.Value.AsInt64() >= (ulong)value2.Value.AsInt64(),
ILOpcode.beq_s => value1.Value.AsInt64() == value2.Value.AsInt64(),
ILOpcode.bne_un_s => value1.Value.AsInt64() != value2.Value.AsInt64(),
ILOpcode.ble_s => value1.Value.AsInt64() <= value2.Value.AsInt64(),
ILOpcode.ble_un_s => (ulong)value1.Value.AsInt64() <= (ulong)value2.Value.AsInt64(),
_ => throw new NotImplementedException() // unreachable
};
}
else if (value1.ValueKind == StackValueKind.Float && value2.ValueKind == StackValueKind.Float)
{
branchTaken = normalizedOpcode switch
{
ILOpcode.blt_s => value1.Value.AsDouble() < value2.Value.AsDouble(),
ILOpcode.blt_un_s => !(value1.Value.AsDouble() >= value2.Value.AsDouble()),
ILOpcode.bgt_s => value1.Value.AsDouble() > value2.Value.AsDouble(),
ILOpcode.bgt_un_s => !(value1.Value.AsDouble() <= value2.Value.AsDouble()),
ILOpcode.bge_s => value1.Value.AsDouble() >= value2.Value.AsDouble(),
ILOpcode.bge_un_s => !(value1.Value.AsDouble() < value2.Value.AsDouble()),
ILOpcode.beq_s => value1.Value.AsDouble() == value2.Value.AsDouble(),
ILOpcode.bne_un_s => value1.Value.AsDouble() != value2.Value.AsDouble(),
ILOpcode.ble_s => value1.Value.AsDouble() <= value2.Value.AsDouble(),
ILOpcode.ble_un_s => !(value1.Value.AsDouble() > value2.Value.AsDouble()),
_ => throw new NotImplementedException() // unreachable
};
}
else
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
}
else
{
Debug.Assert(normalizedOpcode == ILOpcode.br_s);
branchTaken = true;
}
if (branchTaken)
{
reader.Seek(target);
}
}
break;
case ILOpcode.switch_:
{
StackEntry val = stack.Pop();
if (val.ValueKind is not StackValueKind.Int32)
ThrowHelper.ThrowInvalidProgramException();
uint target = (uint)val.Value.AsInt32();
uint count = reader.ReadILUInt32();
int nextInstruction = reader.Offset + (int)(4 * count);
if (target > count)
{
reader.Seek(nextInstruction);
}
else
{
reader.Seek(reader.Offset + (int)(4 * target));
reader.Seek(nextInstruction + (int)reader.ReadILUInt32());
}
}
break;
case ILOpcode.leave:
case ILOpcode.leave_s:
{
stack.Clear();
// We assume no finally regions (would have to run them here)
// This is validated before, but we're being paranoid.
foreach (ILExceptionRegion ehRegion in ehRegions)
{
Debug.Assert(ehRegion.Kind != ILExceptionRegionKind.Finally);
}
int delta = opcode == ILOpcode.leave ?
(int)reader.ReadILUInt32() :
(sbyte)reader.ReadILByte();
int target = reader.Offset + delta;
if (target < 0
|| target > reader.Size)
{
ThrowHelper.ThrowInvalidProgramException();
}
reader.Seek(target);
}
break;
case ILOpcode.clt:
case ILOpcode.clt_un:
case ILOpcode.cgt:
case ILOpcode.cgt_un:
{
StackEntry value1 = stack.Pop();
StackEntry value2 = stack.Pop();
bool condition;
if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32)
{
if (opcode == ILOpcode.cgt)
condition = value1.Value.AsInt32() < value2.Value.AsInt32();
else if (opcode == ILOpcode.cgt_un)
condition = (uint)value1.Value.AsInt32() < (uint)value2.Value.AsInt32();
else if (opcode == ILOpcode.clt)
condition = value1.Value.AsInt32() > value2.Value.AsInt32();
else if (opcode == ILOpcode.clt_un)
condition = (uint)value1.Value.AsInt32() > (uint)value2.Value.AsInt32();
else
return Status.Fail(methodIL.OwningMethod, opcode);
}
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();
else if (opcode == ILOpcode.cgt_un)
condition = (ulong)value1.Value.AsInt64() < (ulong)value2.Value.AsInt64();
else if (opcode == ILOpcode.clt)
condition = value1.Value.AsInt64() > value2.Value.AsInt64();
else if (opcode == ILOpcode.clt_un)
condition = (ulong)value1.Value.AsInt64() > (ulong)value2.Value.AsInt64();
else
return Status.Fail(methodIL.OwningMethod, opcode);
}
else if (value1.ValueKind == StackValueKind.Float && value2.ValueKind == StackValueKind.Float)
{
if (opcode == ILOpcode.cgt)
condition = value1.Value.AsDouble() < value2.Value.AsDouble();
else if (opcode == ILOpcode.cgt_un)
condition = !(value1.Value.AsDouble() >= value2.Value.AsDouble());
else if (opcode == ILOpcode.clt)
condition = value1.Value.AsDouble() > value2.Value.AsDouble();
else if (opcode == ILOpcode.clt_un)
condition = !(value1.Value.AsDouble() <= value2.Value.AsDouble());
else
return Status.Fail(methodIL.OwningMethod, opcode);
}
else if (value1.ValueKind == StackValueKind.ObjRef && value2.ValueKind == StackValueKind.ObjRef)
{
if (opcode == ILOpcode.cgt_un)
condition = value1.Value == null && value2.Value != null;
else
return Status.Fail(methodIL.OwningMethod, opcode);
}
else
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
stack.Push(StackValueKind.Int32, condition
? ValueTypeValue.FromInt32(1)
: ValueTypeValue.FromInt32(0));
}
break;
case ILOpcode.ceq:
{
StackEntry value1 = stack.Pop();
StackEntry value2 = stack.Pop();
if (value1.ValueKind == value2.ValueKind)
{
stack.Push(StackValueKind.Int32,
Value.Equals(value1.Value, value2.Value)
? ValueTypeValue.FromInt32(1)
: ValueTypeValue.FromInt32(0));
}
else
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
}
break;
case ILOpcode.neg:
{
StackEntry value = stack.Pop();
if (value.ValueKind == StackValueKind.Int32)
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(-value.Value.AsInt32()));
else
return Status.Fail(methodIL.OwningMethod, opcode);
}
break;
case ILOpcode.or:
case ILOpcode.shl:
case ILOpcode.add:
case ILOpcode.sub:
case ILOpcode.mul:
case ILOpcode.and:
case ILOpcode.div:
case ILOpcode.div_un:
case ILOpcode.rem:
case ILOpcode.rem_un:
{
bool isDivRem = opcode == ILOpcode.div || opcode == ILOpcode.div_un
|| opcode == ILOpcode.rem || opcode == ILOpcode.rem_un;
StackEntry value2 = stack.Pop();
StackEntry value1 = stack.Pop();
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");
int result = opcode switch
{
ILOpcode.or => value1.Value.AsInt32() | value2.Value.AsInt32(),
ILOpcode.shl => 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(),
ILOpcode.mul => value1.Value.AsInt32() * value2.Value.AsInt32(),
ILOpcode.div => value1.Value.AsInt32() / value2.Value.AsInt32(),
ILOpcode.div_un => (int)((uint)value1.Value.AsInt32() / (uint)value2.Value.AsInt32()),
ILOpcode.rem => value1.Value.AsInt32() % value2.Value.AsInt32(),
ILOpcode.rem_un => (int)((uint)value1.Value.AsInt32() % (uint)value2.Value.AsInt32()),
_ => throw new NotImplementedException(), // unreachable
};
stack.Push(isNint ? StackValueKind.NativeInt : StackValueKind.Int32, ValueTypeValue.FromInt32(result));
}
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");
long result = opcode switch
{
ILOpcode.or => 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(),
ILOpcode.mul => value1.Value.AsInt64() * value2.Value.AsInt64(),
ILOpcode.div => value1.Value.AsInt64() / value2.Value.AsInt64(),
ILOpcode.div_un => (long)((ulong)value1.Value.AsInt64() / (ulong)value2.Value.AsInt64()),
ILOpcode.rem => value1.Value.AsInt64() % value2.Value.AsInt64(),
ILOpcode.rem_un => (long)((ulong)value1.Value.AsInt64() % (ulong)value2.Value.AsInt64()),
_ => throw new NotImplementedException(), // unreachable
};
stack.Push(isNint ? StackValueKind.NativeInt : StackValueKind.Int64, ValueTypeValue.FromInt64(result));
}
else if (value1.ValueKind == StackValueKind.Float && value2.ValueKind == StackValueKind.Float)
{
if (isDivRem && value2.Value.AsDouble() == 0)
return Status.Fail(methodIL.OwningMethod, opcode, "Division by zero");
if (opcode == ILOpcode.or || opcode == ILOpcode.shl || opcode == ILOpcode.and || opcode == ILOpcode.div_un || opcode == ILOpcode.rem_un)
ThrowHelper.ThrowInvalidProgramException();
double result = opcode switch
{
ILOpcode.add => value1.Value.AsDouble() + value2.Value.AsDouble(),
ILOpcode.sub => value1.Value.AsDouble() - value2.Value.AsDouble(),
ILOpcode.mul => value1.Value.AsDouble() * value2.Value.AsDouble(),
ILOpcode.div => value1.Value.AsDouble() / value2.Value.AsDouble(),
ILOpcode.rem => value1.Value.AsDouble() % value2.Value.AsDouble(),
_ => throw new NotImplementedException(), // unreachable
};
stack.Push(StackValueKind.Float, ValueTypeValue.FromDouble(result));
}
else if (value1.ValueKind == StackValueKind.Int64 && value2.ValueKind == StackValueKind.Int32
&& opcode == ILOpcode.shl)
{
long result = value1.Value.AsInt64() << value2.Value.AsInt32();
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
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
}
break;
case ILOpcode.ldlen:
{
StackEntry popped = stack.Pop();
if (popped.Value is ArrayInstance arrayInstance)
{
stack.Push(StackValueKind.NativeInt, context.Target.PointerSize == 8 ? ValueTypeValue.FromInt64(arrayInstance.Length) : ValueTypeValue.FromInt32(arrayInstance.Length));
}
else if (popped.Value == null)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Null array");
}
else
{
ThrowHelper.ThrowInvalidProgramException();
}
}
break;
case ILOpcode.stelem:
case ILOpcode.stelem_i:
case ILOpcode.stelem_i1:
case ILOpcode.stelem_i2:
case ILOpcode.stelem_i4:
case ILOpcode.stelem_i8:
case ILOpcode.stelem_r4:
case ILOpcode.stelem_r8:
{
TypeDesc elementType = opcode switch
{
ILOpcode.stelem_i => context.GetWellKnownType(WellKnownType.IntPtr),
ILOpcode.stelem_i1 => context.GetWellKnownType(WellKnownType.SByte),
ILOpcode.stelem_i2 => context.GetWellKnownType(WellKnownType.Int16),
ILOpcode.stelem_i4 => context.GetWellKnownType(WellKnownType.Int32),
ILOpcode.stelem_i8 => context.GetWellKnownType(WellKnownType.Int64),
ILOpcode.stelem_r4 => context.GetWellKnownType(WellKnownType.Single),
ILOpcode.stelem_r8 => context.GetWellKnownType(WellKnownType.Double),
_ => (TypeDesc)methodIL.GetObject(reader.ReadILToken()),
};
if (elementType.IsGCPointer)
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
Value value = stack.PopIntoLocation(elementType);
if (!stack.TryPopIntValue(out int index))
{
ThrowHelper.ThrowInvalidProgramException();
}
StackEntry array = stack.Pop();
if (array.Value is ArrayInstance arrayInstance)
{
if (!arrayInstance.TryStoreElement(index, value))
return Status.Fail(methodIL.OwningMethod, opcode, "Out of range access");
}
else if (array.Value == null)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Null array");
}
else
{
ThrowHelper.ThrowInvalidProgramException();
}
}
break;
case ILOpcode.ldelem:
case ILOpcode.ldelem_i:
case ILOpcode.ldelem_i1:
case ILOpcode.ldelem_u1:
case ILOpcode.ldelem_i2:
case ILOpcode.ldelem_u2:
case ILOpcode.ldelem_i4:
case ILOpcode.ldelem_u4:
case ILOpcode.ldelem_i8:
case ILOpcode.ldelem_r4:
case ILOpcode.ldelem_r8:
{
TypeDesc elementType = opcode switch
{
ILOpcode.ldelem_i => context.GetWellKnownType(WellKnownType.IntPtr),
ILOpcode.ldelem_i1 => context.GetWellKnownType(WellKnownType.SByte),
ILOpcode.ldelem_u1 => context.GetWellKnownType(WellKnownType.Byte),
ILOpcode.ldelem_i2 => context.GetWellKnownType(WellKnownType.Int16),
ILOpcode.ldelem_u2 => context.GetWellKnownType(WellKnownType.UInt16),
ILOpcode.ldelem_i4 => context.GetWellKnownType(WellKnownType.Int32),
ILOpcode.ldelem_u4 => context.GetWellKnownType(WellKnownType.UInt32),
ILOpcode.ldelem_i8 => context.GetWellKnownType(WellKnownType.Int64),
ILOpcode.ldelem_r4 => context.GetWellKnownType(WellKnownType.Single),
ILOpcode.ldelem_r8 => context.GetWellKnownType(WellKnownType.Double),
_ => (TypeDesc)methodIL.GetObject(reader.ReadILToken()),
};
if (elementType.IsGCPointer)
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
if (!stack.TryPopIntValue(out int index))
{
ThrowHelper.ThrowInvalidProgramException();
}
StackEntry array = stack.Pop();
if (array.Value is ArrayInstance arrayInstance)
{
if (!arrayInstance.TryLoadElement(index, out Value value))
return Status.Fail(methodIL.OwningMethod, opcode, "Out of range access");
stack.PushFromLocation(elementType, value);
}
else if (array.Value == null)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Null array");
}
else if (array.Value is ForeignTypeInstance)
{
return Status.Fail(methodIL.OwningMethod, opcode, "Foreign array");
}
else
{
ThrowHelper.ThrowInvalidProgramException();
}
}
break;
case ILOpcode.box:
{
TypeDesc type = (TypeDesc)methodIL.GetObject(reader.ReadILToken());
if (type.IsValueType)
{
if (type.IsNullable)
return Status.Fail(methodIL.OwningMethod, opcode);
if (type.RequiresAlign8())
return Status.Fail(methodIL.OwningMethod, opcode, "Align8");
Value value = stack.PopIntoLocation(type);
AllocationSite allocSite = new AllocationSite(_type, instructionCounter);
if (!ObjectInstance.TryBox((DefType)type, value, allocSite, out ObjectInstance boxedResult))
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
stack.Push(boxedResult);
}
}
break;
case ILOpcode.unbox_any:
{
TypeDesc type = (TypeDesc)methodIL.GetObject(reader.ReadILToken());
StackEntry entry = stack.Pop();
if (entry.Value is ObjectInstance objInst
&& objInst.TryUnboxAny(type, out Value unboxed))
{
stack.PushFromLocation(type, unboxed);
}
else
{
ThrowHelper.ThrowInvalidProgramException();
}
}
break;
case ILOpcode.ldobj:
case ILOpcode.ldind_i1:
case ILOpcode.ldind_u1:
case ILOpcode.ldind_i2:
case ILOpcode.ldind_u2:
case ILOpcode.ldind_i4:
case ILOpcode.ldind_u4:
case ILOpcode.ldind_i8:
{
if (opcode == ILOpcode.ldobj)
{
TypeDesc type = methodIL.GetObject(reader.ReadILToken()) as TypeDesc;
opcode = type.Category switch
{
TypeFlags.SByte => ILOpcode.ldind_i1,
TypeFlags.Boolean or TypeFlags.Byte => ILOpcode.ldind_u1,
TypeFlags.Int16 => ILOpcode.ldind_i2,
TypeFlags.Char or TypeFlags.UInt16 => ILOpcode.ldind_u2,
TypeFlags.Int32 => ILOpcode.ldind_i4,
TypeFlags.UInt32 => ILOpcode.ldind_u4,
TypeFlags.Int64 or TypeFlags.UInt64 => ILOpcode.ldind_i8,
TypeFlags.Single => ILOpcode.ldind_r4,
TypeFlags.Double => ILOpcode.ldind_r8,
_ => ILOpcode.ldobj,
};
if (opcode == ILOpcode.ldobj)
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
}
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:
case ILOpcode.ldind_u4:
stack.Push(StackValueKind.Int32, ValueTypeValue.FromInt32(byRefVal.DereferenceAsInt32()));
break;
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.stobj:
case ILOpcode.stind_i1:
case ILOpcode.stind_i2:
case ILOpcode.stind_i4:
case ILOpcode.stind_i8:
{
if (opcode == ILOpcode.stobj)
{
TypeDesc type = methodIL.GetObject(reader.ReadILToken()) as TypeDesc;
opcode = type.Category switch
{
TypeFlags.SByte or TypeFlags.Boolean or TypeFlags.Byte => ILOpcode.stind_i1,
TypeFlags.Int16 or TypeFlags.Char or TypeFlags.UInt16 => ILOpcode.stind_i2,
TypeFlags.Int32 or TypeFlags.UInt32 => ILOpcode.stind_i4,
TypeFlags.Int64 or TypeFlags.UInt64 => ILOpcode.stind_i8,
_ => ILOpcode.stobj,
};
if (opcode == ILOpcode.stobj)
{
return Status.Fail(methodIL.OwningMethod, opcode);
}
}
Value val = opcode switch
{
ILOpcode.stind_i1 => stack.PopIntoLocation(context.GetWellKnownType(WellKnownType.Byte)),
ILOpcode.stind_i2 => stack.PopIntoLocation(context.GetWellKnownType(WellKnownType.UInt16)),
ILOpcode.stind_i4 => stack.PopIntoLocation(context.GetWellKnownType(WellKnownType.UInt32)),
ILOpcode.stind_i8 => stack.PopIntoLocation(context.GetWellKnownType(WellKnownType.UInt64)),
_ => throw new NotImplementedException()
};
StackEntry location = stack.Pop();
if (location.ValueKind != StackValueKind.ByRef)
ThrowHelper.ThrowInvalidProgramException();
byte[] dest = ((ByRefValue)location.Value).PointedToBytes;
int destOffset = ((ByRefValue)location.Value).PointedToOffset;
byte[] src = ((ValueTypeValue)val).InstanceBytes;
if (destOffset + src.Length > dest.Length)
return Status.Fail(methodIL.OwningMethod, "Out of bound access");
Array.Copy(src, 0, dest, destOffset, src.Length);
}
break;
case ILOpcode.constrained:
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);
}
}
return Status.Fail(methodIL.OwningMethod, "Control fell through");
}