|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic.Utils;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using static System.Linq.Expressions.CachedReflectionInfo;
using AstUtils = System.Linq.Expressions.Utils;
namespace System.Linq.Expressions.Interpreter
{
internal sealed class ExceptionFilter
{
public readonly int LabelIndex;
public readonly int StartIndex;
public readonly int EndIndex;
internal ExceptionFilter(int labelIndex, int start, int end)
{
LabelIndex = labelIndex;
StartIndex = start;
EndIndex = end;
}
}
internal sealed class ExceptionHandler
{
private readonly Type _exceptionType;
public readonly int LabelIndex;
public readonly int HandlerStartIndex;
public readonly int HandlerEndIndex;
public readonly ExceptionFilter? Filter;
internal ExceptionHandler(int labelIndex, int handlerStartIndex, int handlerEndIndex, Type exceptionType, ExceptionFilter? filter)
{
Debug.Assert(exceptionType != null);
LabelIndex = labelIndex;
_exceptionType = exceptionType;
HandlerStartIndex = handlerStartIndex;
HandlerEndIndex = handlerEndIndex;
Filter = filter;
}
public bool Matches(Type exceptionType) => _exceptionType.IsAssignableFrom(exceptionType);
public override string ToString() =>
string.Create(CultureInfo.InvariantCulture, $"catch ({_exceptionType.Name}) [{HandlerStartIndex}->{HandlerEndIndex}]");
}
internal sealed class TryCatchFinallyHandler
{
internal readonly int TryStartIndex;
internal readonly int TryEndIndex;
internal readonly int FinallyStartIndex;
internal readonly int FinallyEndIndex;
internal readonly int GotoEndTargetIndex;
private readonly ExceptionHandler[]? _handlers;
internal bool IsFinallyBlockExist
{
get
{
Debug.Assert((FinallyStartIndex != Instruction.UnknownInstrIndex) == (FinallyEndIndex != Instruction.UnknownInstrIndex));
return FinallyStartIndex != Instruction.UnknownInstrIndex;
}
}
internal ExceptionHandler[]? Handlers => _handlers;
internal bool IsCatchBlockExist => _handlers != null;
/// <summary>
/// No finally block
/// </summary>
internal TryCatchFinallyHandler(int tryStart, int tryEnd, int gotoEndTargetIndex, ExceptionHandler[] handlers)
: this(tryStart, tryEnd, gotoEndTargetIndex, Instruction.UnknownInstrIndex, Instruction.UnknownInstrIndex, handlers)
{
Debug.Assert(handlers != null, "catch blocks should exist");
}
/// <summary>
/// Generic constructor
/// </summary>
internal TryCatchFinallyHandler(int tryStart, int tryEnd, int gotoEndLabelIndex, int finallyStart, int finallyEnd, ExceptionHandler[]? handlers)
{
TryStartIndex = tryStart;
TryEndIndex = tryEnd;
FinallyStartIndex = finallyStart;
FinallyEndIndex = finallyEnd;
GotoEndTargetIndex = gotoEndLabelIndex;
_handlers = handlers;
}
internal bool HasHandler(InterpretedFrame frame, Exception exception, [NotNullWhen(true)] out ExceptionHandler? handler, out object? unwrappedException)
{
#if DEBUG
if (exception is RethrowException)
{
// Unreachable.
// Want to assert that this case isn't hit, but an assertion failure here will be eaten because
// we are in an exception filter. Therefore return true here and assert in the catch block.
handler = null!;
unwrappedException = exception;
return true;
}
#endif
frame.SaveTraceToException(exception);
if (IsCatchBlockExist)
{
RuntimeWrappedException? rwe = exception as RuntimeWrappedException;
unwrappedException = rwe != null ? rwe.WrappedException : exception;
Type exceptionType = unwrappedException.GetType();
foreach (ExceptionHandler candidate in _handlers!)
{
if (candidate.Matches(exceptionType) && (candidate.Filter == null || FilterPasses(frame, ref unwrappedException, candidate.Filter)))
{
handler = candidate;
return true;
}
}
}
else
{
unwrappedException = null;
}
handler = null;
return false;
}
private static bool FilterPasses(InterpretedFrame frame, ref object? exception, ExceptionFilter filter)
{
Interpreter interpreter = frame.Interpreter;
Instruction[] instructions = interpreter.Instructions.Instructions;
int stackIndex = frame.StackIndex;
int frameIndex = frame.InstructionIndex;
try
{
int index = interpreter._labels[filter.LabelIndex].Index;
frame.InstructionIndex = index;
frame.Push(exception);
while (index >= filter.StartIndex && index < filter.EndIndex)
{
index += instructions[index].Run(frame);
frame.InstructionIndex = index;
}
// Exception is stored in a local at start of the filter, and loaded from it at the end, so it is now
// on the top of the stack. It may have been assigned to in the course of the filter running.
// If this is the handler that will be executed, then if the filter has assigned to the exception variable
// that change should be visible to the handler. Otherwise, it should not, so we write it back only on true.
object? exceptionLocal = frame.Pop();
if ((bool)frame.Pop()!)
{
exception = exceptionLocal;
// Stack and instruction indices will be overwritten in the catch block anyway, so no need to restore.
return true;
}
}
catch
{
// Silently eating exceptions and returning false matches the CLR behavior.
}
frame.StackIndex = stackIndex;
frame.InstructionIndex = frameIndex;
return false;
}
}
internal sealed class TryFaultHandler
{
internal readonly int TryStartIndex;
internal readonly int TryEndIndex;
internal readonly int FinallyStartIndex;
internal readonly int FinallyEndIndex;
internal TryFaultHandler(int tryStart, int tryEnd, int finallyStart, int finallyEnd)
{
TryStartIndex = tryStart;
TryEndIndex = tryEnd;
FinallyStartIndex = finallyStart;
FinallyEndIndex = finallyEnd;
}
}
/// <summary>
/// The re-throw instruction will throw this exception
/// </summary>
[Serializable]
internal sealed class RethrowException : Exception
{
public RethrowException() : base() { }
[Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
internal RethrowException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}
internal sealed class DebugInfo
{
public int StartLine, EndLine;
public int Index;
public string? FileName;
public bool IsClear;
private static readonly DebugInfoComparer s_debugComparer = new DebugInfoComparer();
private sealed class DebugInfoComparer : IComparer<DebugInfo>
{
//We allow comparison between int and DebugInfo here
int IComparer<DebugInfo>.Compare(DebugInfo? d1, DebugInfo? d2)
{
Debug.Assert(d1 != null && d2 != null);
if (d1.Index > d2.Index) return 1;
else if (d1.Index == d2.Index) return 0;
else return -1;
}
}
public static DebugInfo? GetMatchingDebugInfo(DebugInfo[] debugInfos, int index)
{
//Create a faked DebugInfo to do the search
var d = new DebugInfo { Index = index };
//to find the closest debug info before the current index
int i = Array.BinarySearch<DebugInfo>(debugInfos, d, s_debugComparer);
if (i < 0)
{
//~i is the index for the first bigger element
//if there is no bigger element, ~i is the length of the array
i = ~i;
if (i == 0)
{
return null;
}
//return the last one that is smaller
i--;
}
return debugInfos[i];
}
public override string ToString()
{
if (IsClear)
{
return string.Create(CultureInfo.InvariantCulture, $"{Index}: clear");
}
else
{
return string.Create(CultureInfo.InvariantCulture, $"{Index}: [{StartLine}-{EndLine}] '{FileName}'");
}
}
}
internal readonly struct InterpretedFrameInfo
{
private readonly string? _methodName;
private readonly DebugInfo? _debugInfo;
public InterpretedFrameInfo(string? methodName, DebugInfo? info)
{
_methodName = methodName;
_debugInfo = info;
}
public override string? ToString() => _debugInfo != null ? _methodName + ": " + _debugInfo : _methodName;
}
internal sealed class LightCompiler
{
private readonly InstructionList _instructions;
private readonly LocalVariables _locals = new LocalVariables();
private readonly List<DebugInfo> _debugInfos = new List<DebugInfo>();
private readonly HybridReferenceDictionary<LabelTarget, LabelInfo> _treeLabels = new HybridReferenceDictionary<LabelTarget, LabelInfo>();
private LabelScopeInfo _labelBlock = new LabelScopeInfo(null, LabelScopeKind.Lambda);
private readonly Stack<ParameterExpression> _exceptionForRethrowStack = new Stack<ParameterExpression>();
private readonly LightCompiler? _parent;
private readonly StackGuard _guard = new StackGuard();
private static readonly LocalDefinition[] s_emptyLocals = Array.Empty<LocalDefinition>();
public LightCompiler()
{
_instructions = new InstructionList();
}
private LightCompiler(LightCompiler parent)
: this()
{
_parent = parent;
}
public InstructionList Instructions => _instructions;
public LightDelegateCreator CompileTop(LambdaExpression node)
{
node.ValidateArgumentCount();
//Console.WriteLine(node.DebugView);
for (int i = 0, n = node.ParameterCount; i < n; i++)
{
ParameterExpression p = node.GetParameter(i);
LocalDefinition local = _locals.DefineLocal(p, 0);
_instructions.EmitInitializeParameter(local.Index);
}
Compile(node.Body);
// pop the result of the last expression:
if (node.Body.Type != typeof(void) && node.ReturnType == typeof(void))
{
_instructions.EmitPop();
}
Debug.Assert(_instructions.CurrentStackDepth == (node.ReturnType != typeof(void) ? 1 : 0));
return new LightDelegateCreator(MakeInterpreter(node.Name), node);
}
private Interpreter MakeInterpreter(string? lambdaName)
{
DebugInfo[] debugInfos = _debugInfos.ToArray();
foreach (KeyValuePair<LabelTarget, LabelInfo> kvp in _treeLabels)
{
kvp.Value.ValidateFinish();
}
return new Interpreter(lambdaName, _locals, _instructions.ToArray(), debugInfos);
}
private void CompileConstantExpression(Expression expr)
{
var node = (ConstantExpression)expr;
_instructions.EmitLoad(node.Value, node.Type);
}
private void CompileDefaultExpression(Expression expr)
{
CompileDefaultExpression(expr.Type);
}
private void CompileDefaultExpression(Type type)
{
if (type != typeof(void))
{
if (type.IsNullableOrReferenceType())
{
_instructions.EmitLoad(value: null);
}
else
{
object? value = ScriptingRuntimeHelpers.GetPrimitiveDefaultValue(type);
if (value != null)
{
_instructions.EmitLoad(value);
}
else
{
_instructions.EmitDefaultValue(type);
}
}
}
}
private LocalVariable EnsureAvailableForClosure(ParameterExpression expr)
{
if (_locals.TryGetLocalOrClosure(expr, out LocalVariable? local))
{
if (!local.InClosure && !local.IsBoxed)
{
_locals.Box(expr, _instructions);
}
return local;
}
else if (_parent != null)
{
_parent.EnsureAvailableForClosure(expr);
return _locals.AddClosureVariable(expr);
}
else
{
throw new InvalidOperationException("unbound variable: " + expr);
}
}
private LocalVariable ResolveLocal(ParameterExpression variable)
{
if (!_locals.TryGetLocalOrClosure(variable, out LocalVariable? local))
{
local = EnsureAvailableForClosure(variable);
}
return local;
}
private void CompileGetVariable(ParameterExpression variable)
{
LoadLocalNoValueTypeCopy(variable);
_instructions.SetDebugCookie(variable.Name);
EmitCopyValueType(variable.Type);
}
private void EmitCopyValueType(Type valueType)
{
if (MaybeMutableValueType(valueType))
{
// loading a value type on the stack has copy semantics unless
// we are specifically loading the address of the object, so we
// emit a copy here if we don't know the type is immutable.
_instructions.Emit(ValueTypeCopyInstruction.Instruction);
}
}
private void LoadLocalNoValueTypeCopy(ParameterExpression variable)
{
LocalVariable local = ResolveLocal(variable);
if (local.InClosure)
{
_instructions.EmitLoadLocalFromClosure(local.Index);
}
else if (local.IsBoxed)
{
_instructions.EmitLoadLocalBoxed(local.Index);
}
else
{
_instructions.EmitLoadLocal(local.Index);
}
}
private static bool MaybeMutableValueType(Type type)
{
return type.IsValueType && !type.IsEnum && !type.IsPrimitive;
}
private void CompileGetBoxedVariable(ParameterExpression variable)
{
LocalVariable local = ResolveLocal(variable);
if (local.InClosure)
{
_instructions.EmitLoadLocalFromClosureBoxed(local.Index);
}
else
{
Debug.Assert(local.IsBoxed);
_instructions.EmitLoadLocal(local.Index);
}
_instructions.SetDebugCookie(variable.Name);
}
private void CompileSetVariable(ParameterExpression variable, bool isVoid)
{
LocalVariable local = ResolveLocal(variable);
if (local.InClosure)
{
if (isVoid)
{
_instructions.EmitStoreLocalToClosure(local.Index);
}
else
{
_instructions.EmitAssignLocalToClosure(local.Index);
}
}
else if (local.IsBoxed)
{
if (isVoid)
{
_instructions.EmitStoreLocalBoxed(local.Index);
}
else
{
_instructions.EmitAssignLocalBoxed(local.Index);
}
}
else
{
if (isVoid)
{
_instructions.EmitStoreLocal(local.Index);
}
else
{
_instructions.EmitAssignLocal(local.Index);
}
}
_instructions.SetDebugCookie(variable.Name);
}
private void CompileParameterExpression(Expression expr)
{
var node = (ParameterExpression)expr;
CompileGetVariable(node);
}
private void CompileBlockExpression(Expression expr, bool asVoid)
{
var node = (BlockExpression)expr;
if (node.ExpressionCount != 0)
{
LocalDefinition[] end = CompileBlockStart(node);
Expression lastExpression = node.Expressions[node.Expressions.Count - 1];
Compile(lastExpression, asVoid);
CompileBlockEnd(end);
}
}
private LocalDefinition[] CompileBlockStart(BlockExpression node)
{
int start = _instructions.Count;
LocalDefinition[] locals;
ReadOnlyCollection<ParameterExpression> variables = node.Variables;
if (variables.Count != 0)
{
// TODO: basic flow analysis so we don't have to initialize all
// variables.
locals = new LocalDefinition[variables.Count];
int localCnt = 0;
foreach (ParameterExpression variable in variables)
{
LocalDefinition local = _locals.DefineLocal(variable, start);
locals[localCnt++] = local;
_instructions.EmitInitializeLocal(local.Index, variable.Type);
_instructions.SetDebugCookie(variable.Name);
}
}
else
{
locals = s_emptyLocals;
}
for (int i = 0; i < node.Expressions.Count - 1; i++)
{
CompileAsVoid(node.Expressions[i]);
}
return locals;
}
private void CompileBlockEnd(LocalDefinition[] locals)
{
foreach (LocalDefinition local in locals)
{
_locals.UndefineLocal(local, _instructions.Count);
}
}
private void CompileIndexExpression(Expression expr)
{
var index = (IndexExpression)expr;
// instance:
if (index.Object != null)
{
EmitThisForMethodCall(index.Object);
}
// indexes, byref args not allowed.
for (int i = 0, n = index.ArgumentCount; i < n; i++)
{
Compile(index.GetArgument(i));
}
EmitIndexGet(index);
}
private void EmitIndexGet(IndexExpression index)
{
if (index.Indexer != null)
{
_instructions.EmitCall(index.Indexer.GetGetMethod(nonPublic: true)!);
}
else if (index.ArgumentCount != 1)
{
_instructions.EmitCall(TypeUtils.GetArrayGetMethod(index.Object!.Type));
}
else
{
_instructions.EmitGetArrayItem();
}
}
private void CompileIndexAssignment(BinaryExpression node, bool asVoid)
{
var index = (IndexExpression)node.Left;
// instance:
if (index.Object != null)
{
EmitThisForMethodCall(index.Object);
}
// indexes, byref args not allowed.
for (int i = 0, n = index.ArgumentCount; i < n; i++)
{
Compile(index.GetArgument(i));
}
// value:
Compile(node.Right);
LocalDefinition local = default(LocalDefinition);
if (!asVoid)
{
local = _locals.DefineLocal(Expression.Parameter(node.Right.Type), _instructions.Count);
_instructions.EmitAssignLocal(local.Index);
}
if (index.Indexer != null)
{
_instructions.EmitCall(index.Indexer.GetSetMethod(nonPublic: true)!);
}
else if (index.ArgumentCount != 1)
{
_instructions.EmitCall(TypeUtils.GetArraySetMethod(index.Object!.Type));
}
else
{
_instructions.EmitSetArrayItem();
}
if (!asVoid)
{
_instructions.EmitLoadLocal(local.Index);
_locals.UndefineLocal(local, _instructions.Count);
}
}
private void CompileMemberAssignment(BinaryExpression node, bool asVoid)
{
var member = (MemberExpression)node.Left;
Expression? expr = member.Expression;
if (expr != null)
{
EmitThisForMethodCall(expr);
}
CompileMemberAssignment(asVoid, member.Member, node.Right, forBinding: false);
}
private void CompileMemberAssignment(bool asVoid, MemberInfo refMember, Expression value, bool forBinding)
{
if (refMember is PropertyInfo pi)
{
MethodInfo method = pi.GetSetMethod(nonPublic: true)!;
if (forBinding && method.IsStatic)
{
throw Error.InvalidProgram();
}
EmitThisForMethodCall(value);
int start = _instructions.Count;
if (!asVoid)
{
LocalDefinition local = _locals.DefineLocal(Expression.Parameter(value.Type), start);
_instructions.EmitAssignLocal(local.Index);
_instructions.EmitCall(method);
_instructions.EmitLoadLocal(local.Index);
_locals.UndefineLocal(local, _instructions.Count);
}
else
{
_instructions.EmitCall(method);
}
}
else
{
// other types inherited from MemberInfo (EventInfo\MethodBase\Type) cannot be used in MemberAssignment
var fi = (FieldInfo)refMember;
Debug.Assert(fi != null);
if (fi.IsLiteral)
{
throw Error.NotSupported();
}
if (forBinding && fi.IsStatic)
{
_instructions.UnEmit(); // Undo having pushed the instance to the stack.
}
EmitThisForMethodCall(value);
int start = _instructions.Count;
if (!asVoid)
{
LocalDefinition local = _locals.DefineLocal(Expression.Parameter(value.Type), start);
_instructions.EmitAssignLocal(local.Index);
_instructions.EmitStoreField(fi);
_instructions.EmitLoadLocal(local.Index);
_locals.UndefineLocal(local, _instructions.Count);
}
else
{
_instructions.EmitStoreField(fi);
}
}
}
private void CompileVariableAssignment(BinaryExpression node, bool asVoid)
{
Compile(node.Right);
var target = (ParameterExpression)node.Left;
CompileSetVariable(target, asVoid);
}
private void CompileAssignBinaryExpression(Expression expr, bool asVoid)
{
var node = (BinaryExpression)expr;
switch (node.Left.NodeType)
{
case ExpressionType.Index:
CompileIndexAssignment(node, asVoid);
break;
case ExpressionType.MemberAccess:
CompileMemberAssignment(node, asVoid);
break;
case ExpressionType.Parameter:
case ExpressionType.Extension:
CompileVariableAssignment(node, asVoid);
break;
default:
throw Error.InvalidLvalue(node.Left.NodeType);
}
}
private void CompileBinaryExpression(Expression expr)
{
var node = (BinaryExpression)expr;
if (node.Method != null)
{
if (node.IsLifted)
{
// lifting: we need to do the null checks for nullable types and reference types. If the value
// is null we return null, or false for a comparison unless it's not equal, in which case we return
// true.
// INCOMPAT: The DLR binder short circuits on comparisons other than equal and not equal,
// but C# doesn't.
BranchLabel end = _instructions.MakeLabel();
LocalDefinition leftTemp = _locals.DefineLocal(Expression.Parameter(node.Left.Type), _instructions.Count);
Compile(node.Left);
_instructions.EmitStoreLocal(leftTemp.Index);
LocalDefinition rightTemp = _locals.DefineLocal(Expression.Parameter(node.Right.Type), _instructions.Count);
Compile(node.Right);
_instructions.EmitStoreLocal(rightTemp.Index);
switch (node.NodeType)
{
case ExpressionType.Equal:
case ExpressionType.NotEqual:
/* generating (equal/not equal):
* if (left == null) {
* right == null/right != null
* }else if (right == null) {
* False/True
* }else{
* op_Equality(left, right)/op_Inequality(left, right)
* }
*/
if (node.IsLiftedToNull)
{
goto default;
}
BranchLabel testRight = _instructions.MakeLabel();
BranchLabel callMethod = _instructions.MakeLabel();
_instructions.EmitLoadLocal(leftTemp.Index);
_instructions.EmitLoad(null, typeof(object));
_instructions.EmitEqual(typeof(object));
_instructions.EmitBranchFalse(testRight);
// left is null
_instructions.EmitLoadLocal(rightTemp.Index);
_instructions.EmitLoad(null, typeof(object));
if (node.NodeType == ExpressionType.Equal)
{
_instructions.EmitEqual(typeof(object));
}
else
{
_instructions.EmitNotEqual(typeof(object));
}
_instructions.EmitBranch(end, hasResult: false, hasValue: true);
_instructions.MarkLabel(testRight);
// left is not null, check right
_instructions.EmitLoadLocal(rightTemp.Index);
_instructions.EmitLoad(null, typeof(object));
_instructions.EmitEqual(typeof(object));
_instructions.EmitBranchFalse(callMethod);
// right null, left not, false
// right null, left not, true
_instructions.EmitLoad(
node.NodeType == ExpressionType.Equal ? AstUtils.BoxedFalse : AstUtils.BoxedTrue,
typeof(bool));
_instructions.EmitBranch(end, hasResult: false, hasValue: true);
// both are not null
_instructions.MarkLabel(callMethod);
_instructions.EmitLoadLocal(leftTemp.Index);
_instructions.EmitLoadLocal(rightTemp.Index);
_instructions.EmitCall(node.Method);
break;
default:
BranchLabel loadDefault = _instructions.MakeLabel();
if (node.Left.Type.IsNullableOrReferenceType())
{
_instructions.EmitLoadLocal(leftTemp.Index);
_instructions.EmitLoad(null, typeof(object));
_instructions.EmitEqual(typeof(object));
_instructions.EmitBranchTrue(loadDefault);
}
if (node.Right.Type.IsNullableOrReferenceType())
{
_instructions.EmitLoadLocal(rightTemp.Index);
_instructions.EmitLoad(null, typeof(object));
_instructions.EmitEqual(typeof(object));
_instructions.EmitBranchTrue(loadDefault);
}
_instructions.EmitLoadLocal(leftTemp.Index);
_instructions.EmitLoadLocal(rightTemp.Index);
_instructions.EmitCall(node.Method);
_instructions.EmitBranch(end, hasResult: false, hasValue: true);
_instructions.MarkLabel(loadDefault);
switch (node.NodeType)
{
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
if (node.IsLiftedToNull)
{
goto default;
}
_instructions.EmitLoad(AstUtils.BoxedFalse, typeof(object));
break;
default:
_instructions.EmitLoad(null, typeof(object));
break;
}
break;
}
_instructions.MarkLabel(end);
_locals.UndefineLocal(leftTemp, _instructions.Count);
_locals.UndefineLocal(rightTemp, _instructions.Count);
}
else
{
Compile(node.Left);
Compile(node.Right);
_instructions.EmitCall(node.Method);
}
}
else
{
switch (node.NodeType)
{
case ExpressionType.ArrayIndex:
Debug.Assert(node.Right.Type == typeof(int));
Compile(node.Left);
Compile(node.Right);
_instructions.EmitGetArrayItem();
return;
case ExpressionType.Add:
case ExpressionType.AddChecked:
case ExpressionType.Subtract:
case ExpressionType.SubtractChecked:
case ExpressionType.Multiply:
case ExpressionType.MultiplyChecked:
case ExpressionType.Divide:
case ExpressionType.Modulo:
CompileArithmetic(node.NodeType, node.Left, node.Right);
return;
case ExpressionType.ExclusiveOr:
Compile(node.Left);
Compile(node.Right);
_instructions.EmitExclusiveOr(node.Left.Type);
break;
case ExpressionType.Or:
Compile(node.Left);
Compile(node.Right);
_instructions.EmitOr(node.Left.Type);
break;
case ExpressionType.And:
Compile(node.Left);
Compile(node.Right);
_instructions.EmitAnd(node.Left.Type);
break;
case ExpressionType.Equal:
CompileEqual(node.Left, node.Right, node.IsLiftedToNull);
return;
case ExpressionType.NotEqual:
CompileNotEqual(node.Left, node.Right, node.IsLiftedToNull);
return;
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
CompileComparison((BinaryExpression)node);
return;
case ExpressionType.LeftShift:
Compile(node.Left);
Compile(node.Right);
_instructions.EmitLeftShift(node.Left.Type);
break;
case ExpressionType.RightShift:
Compile(node.Left);
Compile(node.Right);
_instructions.EmitRightShift(node.Left.Type);
break;
default:
throw new PlatformNotSupportedException(SR.Format(SR.UnsupportedExpressionType, node.NodeType));
}
}
}
#if DEBUG
private static bool IsNullComparison(Expression left, Expression right)
{
return IsNullConstant(left)
? !IsNullConstant(right) && right.Type.IsNullableType()
: IsNullConstant(right) && left.Type.IsNullableType();
}
private static bool IsNullConstant(Expression e)
{
var c = e as ConstantExpression;
return c != null && c.Value == null;
}
#endif
private void CompileEqual(Expression left, Expression right, bool liftedToNull)
{
#if DEBUG
Debug.Assert(IsNullComparison(left, right) || left.Type == right.Type || !left.Type.IsValueType && !right.Type.IsValueType);
#endif
Compile(left);
Compile(right);
_instructions.EmitEqual(left.Type, liftedToNull);
}
private void CompileNotEqual(Expression left, Expression right, bool liftedToNull)
{
#if DEBUG
Debug.Assert(IsNullComparison(left, right) || left.Type == right.Type || !left.Type.IsValueType && !right.Type.IsValueType);
#endif
Compile(left);
Compile(right);
_instructions.EmitNotEqual(left.Type, liftedToNull);
}
private void CompileComparison(BinaryExpression node)
{
Expression left = node.Left;
Expression right = node.Right;
Debug.Assert(left.Type == right.Type && left.Type.IsNumeric());
Compile(left);
Compile(right);
switch (node.NodeType)
{
case ExpressionType.LessThan: _instructions.EmitLessThan(left.Type, node.IsLiftedToNull); break;
case ExpressionType.LessThanOrEqual: _instructions.EmitLessThanOrEqual(left.Type, node.IsLiftedToNull); break;
case ExpressionType.GreaterThan: _instructions.EmitGreaterThan(left.Type, node.IsLiftedToNull); break;
case ExpressionType.GreaterThanOrEqual: _instructions.EmitGreaterThanOrEqual(left.Type, node.IsLiftedToNull); break;
default: throw ContractUtils.Unreachable;
}
}
private void CompileArithmetic(ExpressionType nodeType, Expression left, Expression right)
{
Debug.Assert(left.Type == right.Type && left.Type.IsArithmetic());
Compile(left);
Compile(right);
switch (nodeType)
{
case ExpressionType.Add: _instructions.EmitAdd(left.Type, @checked: false); break;
case ExpressionType.AddChecked: _instructions.EmitAdd(left.Type, @checked: true); break;
case ExpressionType.Subtract: _instructions.EmitSub(left.Type, @checked: false); break;
case ExpressionType.SubtractChecked: _instructions.EmitSub(left.Type, @checked: true); break;
case ExpressionType.Multiply: _instructions.EmitMul(left.Type, @checked: false); break;
case ExpressionType.MultiplyChecked: _instructions.EmitMul(left.Type, @checked: true); break;
case ExpressionType.Divide: _instructions.EmitDiv(left.Type); break;
case ExpressionType.Modulo: _instructions.EmitModulo(left.Type); break;
default: throw ContractUtils.Unreachable;
}
}
private void CompileConvertUnaryExpression(Expression expr)
{
var node = (UnaryExpression)expr;
if (node.Method != null)
{
BranchLabel end = _instructions.MakeLabel();
BranchLabel loadDefault = _instructions.MakeLabel();
MethodInfo method = node.Method;
ParameterInfo[] parameters = method.GetParametersCached();
Debug.Assert(parameters.Length == 1);
ParameterInfo parameter = parameters[0];
Expression operand = node.Operand;
Type operandType = operand.Type;
LocalDefinition opTemp = _locals.DefineLocal(Expression.Parameter(operandType), _instructions.Count);
ByRefUpdater? updater = null;
Type parameterType = parameter.ParameterType;
if (parameterType.IsByRef)
{
if (node.IsLifted)
{
Compile(node.Operand);
}
else
{
updater = CompileAddress(node.Operand, 0);
parameterType = parameterType.GetElementType()!;
}
}
else
{
Compile(node.Operand);
}
_instructions.EmitStoreLocal(opTemp.Index);
if (!operandType.IsValueType || operandType.IsNullableType() && node.IsLiftedToNull)
{
_instructions.EmitLoadLocal(opTemp.Index);
_instructions.EmitLoad(null, typeof(object));
_instructions.EmitEqual(typeof(object));
_instructions.EmitBranchTrue(loadDefault);
}
_instructions.EmitLoadLocal(opTemp.Index);
if (operandType.IsNullableType() && parameterType.Equals(operandType.GetNonNullableType()))
{
_instructions.Emit(NullableMethodCallInstruction.CreateGetValue());
}
if (updater == null)
{
_instructions.EmitCall(method);
}
else
{
_instructions.EmitByRefCall(method, parameters, new[] { updater });
updater.UndefineTemps(_instructions, _locals);
}
_instructions.EmitBranch(end, hasResult: false, hasValue: true);
_instructions.MarkLabel(loadDefault);
_instructions.EmitLoad(null, typeof(object));
_instructions.MarkLabel(end);
_locals.UndefineLocal(opTemp, _instructions.Count);
}
else if (node.Type == typeof(void))
{
CompileAsVoid(node.Operand);
}
else
{
Compile(node.Operand);
CompileConvertToType(node.Operand.Type, node.Type, node.NodeType == ExpressionType.ConvertChecked, node.IsLiftedToNull);
}
}
private void CompileConvertToType(Type typeFrom, Type typeTo, bool isChecked, bool isLiftedToNull)
{
Debug.Assert(typeFrom != typeof(void) && typeTo != typeof(void));
if (typeTo.Equals(typeFrom))
{
return;
}
if (typeFrom.IsValueType &&
typeTo.IsNullableType() &&
typeTo.GetNonNullableType().Equals(typeFrom))
{
// VT -> vt?, no conversion necessary
return;
}
if (typeTo.IsValueType &&
typeFrom.IsNullableType() &&
typeFrom.GetNonNullableType().Equals(typeTo))
{
// VT? -> vt, call get_Value
_instructions.Emit(NullableMethodCallInstruction.CreateGetValue());
return;
}
Type nonNullableFrom = typeFrom.GetNonNullableType();
Type nonNullableTo = typeTo.GetNonNullableType();
// use numeric conversions for both numeric types and enums
if ((nonNullableFrom.IsNumericOrBool() || nonNullableFrom.IsEnum)
&& (nonNullableTo.IsNumericOrBool() || nonNullableTo.IsEnum || nonNullableTo == typeof(decimal)))
{
Type? enumTypeTo = null;
if (nonNullableFrom.IsEnum)
{
nonNullableFrom = Enum.GetUnderlyingType(nonNullableFrom);
}
if (nonNullableTo.IsEnum)
{
enumTypeTo = nonNullableTo;
nonNullableTo = Enum.GetUnderlyingType(nonNullableTo);
}
TypeCode from = nonNullableFrom.GetTypeCode();
TypeCode to = nonNullableTo.GetTypeCode();
if (from == to)
{
if (enumTypeTo is not null)
{
// If casting between enums of the same underlying type or to enum from the underlying
// type, there's no need for the numeric conversion, so just include a null-check if
// appropriate.
if (typeFrom.IsNullableType() && !typeTo.IsNullableType())
{
_instructions.Emit(NullableMethodCallInstruction.CreateGetValue());
}
}
else
{
// Casting to the underlying check still needs a numeric conversion to force the type
// change that EmitCastToEnum provides for enums, but needs only one cast. Checked can
// also never throw, so always be unchecked.
_instructions.EmitConvertToUnderlying(to, isLiftedToNull);
}
}
else
{
if (isChecked)
{
_instructions.EmitNumericConvertChecked(from, to, isLiftedToNull);
}
else
{
_instructions.EmitNumericConvertUnchecked(from, to, isLiftedToNull);
}
}
if (enumTypeTo is not null)
{
// Convert from underlying to the enum
_instructions.EmitCastToEnum(enumTypeTo);
}
return;
}
if (typeTo.IsEnum)
{
_instructions.Emit(NullCheckInstruction.Instance);
_instructions.EmitCastReferenceToEnum(typeTo);
return;
}
if (typeTo == typeof(object) || typeTo.IsAssignableFrom(typeFrom))
{
// Conversions to a super-class or implemented interfaces are no-op.
return;
}
// A conversion to a non-implemented interface or an unrelated class, etc. should fail.
_instructions.EmitCast(typeTo);
}
private void CompileNotExpression(UnaryExpression node)
{
Compile(node.Operand);
_instructions.EmitNot(node.Operand.Type);
}
private void CompileUnaryExpression(Expression expr)
{
var node = (UnaryExpression)expr;
if (node.Method != null)
{
EmitUnaryMethodCall(node);
}
else
{
switch (node.NodeType)
{
case ExpressionType.Not:
case ExpressionType.OnesComplement:
CompileNotExpression(node);
break;
case ExpressionType.TypeAs:
CompileTypeAsExpression(node);
break;
case ExpressionType.ArrayLength:
Compile(node.Operand);
_instructions.EmitArrayLength();
break;
case ExpressionType.NegateChecked:
Compile(node.Operand);
_instructions.EmitNegateChecked(node.Type);
break;
case ExpressionType.Negate:
Compile(node.Operand);
_instructions.EmitNegate(node.Type);
break;
case ExpressionType.Increment:
Compile(node.Operand);
_instructions.EmitIncrement(node.Type);
break;
case ExpressionType.Decrement:
Compile(node.Operand);
_instructions.EmitDecrement(node.Type);
break;
case ExpressionType.UnaryPlus:
Compile(node.Operand);
break;
case ExpressionType.IsTrue:
case ExpressionType.IsFalse:
EmitUnaryBoolCheck(node);
break;
default:
throw new PlatformNotSupportedException(SR.Format(SR.UnsupportedExpressionType, node.NodeType));
}
}
}
private void EmitUnaryMethodCall(UnaryExpression node)
{
Compile(node.Operand);
if (node.IsLifted)
{
BranchLabel notNull = _instructions.MakeLabel();
BranchLabel computed = _instructions.MakeLabel();
_instructions.EmitCoalescingBranch(notNull);
_instructions.EmitBranch(computed);
_instructions.MarkLabel(notNull);
_instructions.EmitCall(node.Method!);
_instructions.MarkLabel(computed);
}
else
{
_instructions.EmitCall(node.Method!);
}
}
private void EmitUnaryBoolCheck(UnaryExpression node)
{
Compile(node.Operand);
if (node.IsLifted)
{
BranchLabel notNull = _instructions.MakeLabel();
BranchLabel computed = _instructions.MakeLabel();
_instructions.EmitCoalescingBranch(notNull);
_instructions.EmitBranch(computed);
_instructions.MarkLabel(notNull);
_instructions.EmitLoad(node.NodeType == ExpressionType.IsTrue);
_instructions.EmitEqual(typeof(bool));
_instructions.MarkLabel(computed);
}
else
{
_instructions.EmitLoad(node.NodeType == ExpressionType.IsTrue);
_instructions.EmitEqual(typeof(bool));
}
}
private void CompileAndAlsoBinaryExpression(Expression expr)
{
CompileLogicalBinaryExpression((BinaryExpression)expr, andAlso: true);
}
private void CompileOrElseBinaryExpression(Expression expr)
{
CompileLogicalBinaryExpression((BinaryExpression)expr, andAlso: false);
}
private void CompileLogicalBinaryExpression(BinaryExpression b, bool andAlso)
{
if (b.Method != null && !b.IsLiftedLogical)
{
CompileMethodLogicalBinaryExpression(b, andAlso);
}
else if (b.Left.Type == typeof(bool?))
{
CompileLiftedLogicalBinaryExpression(b, andAlso);
}
else if (b.IsLiftedLogical)
{
Compile(b.ReduceUserdefinedLifted());
}
else
{
CompileUnliftedLogicalBinaryExpression(b, andAlso);
}
}
private void CompileMethodLogicalBinaryExpression(BinaryExpression expr, bool andAlso)
{
BranchLabel labEnd = _instructions.MakeLabel();
Compile(expr.Left);
_instructions.EmitDup();
MethodInfo? opTrue = TypeUtils.GetBooleanOperator(expr.Method!.DeclaringType!, andAlso ? "op_False" : "op_True");
Debug.Assert(opTrue != null, "factory should check that the method exists");
_instructions.EmitCall(opTrue);
_instructions.EmitBranchTrue(labEnd);
Compile(expr.Right);
Debug.Assert(expr.Method.IsStatic);
_instructions.EmitCall(expr.Method);
_instructions.MarkLabel(labEnd);
}
private void CompileLiftedLogicalBinaryExpression(BinaryExpression node, bool andAlso)
{
BranchLabel computeRight = _instructions.MakeLabel();
BranchLabel returnFalse = _instructions.MakeLabel();
BranchLabel returnNull = _instructions.MakeLabel();
BranchLabel returnValue = _instructions.MakeLabel();
LocalDefinition result = _locals.DefineLocal(Expression.Parameter(node.Left.Type), _instructions.Count);
LocalDefinition leftTemp = _locals.DefineLocal(Expression.Parameter(node.Left.Type), _instructions.Count);
Compile(node.Left);
_instructions.EmitStoreLocal(leftTemp.Index);
_instructions.EmitLoadLocal(leftTemp.Index);
_instructions.EmitLoad(null, typeof(object));
_instructions.EmitEqual(typeof(object));
_instructions.EmitBranchTrue(computeRight);
_instructions.EmitLoadLocal(leftTemp.Index);
if (andAlso)
{
_instructions.EmitBranchFalse(returnFalse);
}
else
{
_instructions.EmitBranchTrue(returnFalse);
}
// compute right
_instructions.MarkLabel(computeRight);
LocalDefinition rightTemp = _locals.DefineLocal(Expression.Parameter(node.Right.Type), _instructions.Count);
Compile(node.Right);
_instructions.EmitStoreLocal(rightTemp.Index);
_instructions.EmitLoadLocal(rightTemp.Index);
_instructions.EmitLoad(null, typeof(object));
_instructions.EmitEqual(typeof(object));
_instructions.EmitBranchTrue(returnNull);
_instructions.EmitLoadLocal(rightTemp.Index);
if (andAlso)
{
_instructions.EmitBranchFalse(returnFalse);
}
else
{
_instructions.EmitBranchTrue(returnFalse);
}
// check left for null again
_instructions.EmitLoadLocal(leftTemp.Index);
_instructions.EmitLoad(null, typeof(object));
_instructions.EmitEqual(typeof(object));
_instructions.EmitBranchTrue(returnNull);
// return true
_instructions.EmitLoad(andAlso ? AstUtils.BoxedTrue : AstUtils.BoxedFalse, typeof(object));
_instructions.EmitStoreLocal(result.Index);
_instructions.EmitBranch(returnValue);
// return false
_instructions.MarkLabel(returnFalse);
_instructions.EmitLoad(andAlso ? AstUtils.BoxedFalse : AstUtils.BoxedTrue, typeof(object));
_instructions.EmitStoreLocal(result.Index);
_instructions.EmitBranch(returnValue);
// return null
_instructions.MarkLabel(returnNull);
_instructions.EmitLoad(null, typeof(object));
_instructions.EmitStoreLocal(result.Index);
_instructions.MarkLabel(returnValue);
_instructions.EmitLoadLocal(result.Index);
_locals.UndefineLocal(leftTemp, _instructions.Count);
_locals.UndefineLocal(rightTemp, _instructions.Count);
_locals.UndefineLocal(result, _instructions.Count);
}
private void CompileUnliftedLogicalBinaryExpression(BinaryExpression expr, bool andAlso)
{
BranchLabel elseLabel = _instructions.MakeLabel();
BranchLabel endLabel = _instructions.MakeLabel();
Compile(expr.Left);
if (andAlso)
{
_instructions.EmitBranchFalse(elseLabel);
}
else
{
_instructions.EmitBranchTrue(elseLabel);
}
Compile(expr.Right);
_instructions.EmitBranch(endLabel, hasResult: false, hasValue: true);
_instructions.MarkLabel(elseLabel);
_instructions.EmitLoad(!andAlso);
_instructions.MarkLabel(endLabel);
}
private void CompileConditionalExpression(Expression expr, bool asVoid)
{
var node = (ConditionalExpression)expr;
Compile(node.Test);
if (node.IfTrue == AstUtils.Empty)
{
BranchLabel endOfFalse = _instructions.MakeLabel();
_instructions.EmitBranchTrue(endOfFalse);
Compile(node.IfFalse, asVoid);
_instructions.MarkLabel(endOfFalse);
}
else
{
BranchLabel endOfTrue = _instructions.MakeLabel();
_instructions.EmitBranchFalse(endOfTrue);
Compile(node.IfTrue, asVoid);
if (node.IfFalse != AstUtils.Empty)
{
BranchLabel endOfFalse = _instructions.MakeLabel();
_instructions.EmitBranch(endOfFalse, false, !asVoid);
_instructions.MarkLabel(endOfTrue);
Compile(node.IfFalse, asVoid);
_instructions.MarkLabel(endOfFalse);
}
else
{
_instructions.MarkLabel(endOfTrue);
}
}
}
private void CompileLoopExpression(Expression expr)
{
var node = (LoopExpression)expr;
PushLabelBlock(LabelScopeKind.Statement);
LabelInfo breakLabel = DefineLabel(node.BreakLabel);
LabelInfo continueLabel = DefineLabel(node.ContinueLabel);
_instructions.MarkLabel(continueLabel.GetLabel(this));
// emit loop body:
CompileAsVoid(node.Body);
// emit loop branch:
_instructions.EmitBranch(continueLabel.GetLabel(this), node.Type != typeof(void), hasValue: false);
_instructions.MarkLabel(breakLabel.GetLabel(this));
PopLabelBlock(LabelScopeKind.Statement);
}
private void CompileSwitchExpression(Expression expr)
{
var node = (SwitchExpression)expr;
if (node.Cases.All(c => c.TestValues.All(t => t is ConstantExpression)))
{
if (node.Cases.Count == 0)
{
// Emit the switch value in case it has side-effects, but as void
// since the value is ignored.
CompileAsVoid(node.SwitchValue);
// Now if there is a default body, it happens unconditionally.
if (node.DefaultBody != null)
{
Compile(node.DefaultBody);
}
else
{
// If there are no cases and no default then the type must be void.
// Assert that earlier validation caught any exceptions to that.
Debug.Assert(node.Type == typeof(void));
}
return;
}
TypeCode switchType = node.SwitchValue.Type.GetTypeCode();
if (node.Comparison == null)
{
switch (switchType)
{
case TypeCode.Int32:
CompileIntSwitchExpression<int>(node);
return;
// the following cases are uncommon,
// so to avoid numerous unnecessary generic
// instantiations of Dictionary<K, V> and related types
// in AOT scenarios, we will just use "object" as the key
// NOTE: this does not actually result in any
// extra boxing since both keys and values
// are already boxed when we get them
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.Int16:
case TypeCode.UInt32:
case TypeCode.UInt64:
case TypeCode.Int64:
CompileIntSwitchExpression<object>(node);
return;
}
}
if (switchType == TypeCode.String)
{
// If we have a comparison other than string equality, bail
MethodInfo? equality = String_op_Equality_String_String;
if (equality != null && !equality.IsStatic)
{
equality = null;
}
if (object.Equals(node.Comparison, equality))
{
CompileStringSwitchExpression(node);
return;
}
}
}
LocalDefinition temp = _locals.DefineLocal(Expression.Parameter(node.SwitchValue.Type), _instructions.Count);
Compile(node.SwitchValue);
_instructions.EmitStoreLocal(temp.Index);
LabelTarget doneLabel = Expression.Label(node.Type, "done");
foreach (SwitchCase @case in node.Cases)
{
foreach (Expression val in @case.TestValues)
{
// temp == val ?
// goto(Body) doneLabel:
// {};
CompileConditionalExpression(
Expression.Condition(
Expression.Equal(temp.Parameter, val, false, node.Comparison),
Expression.Goto(doneLabel, @case.Body),
AstUtils.Empty
),
asVoid: true);
}
}
// doneLabel(DefaultBody):
CompileLabelExpression(Expression.Label(doneLabel, node.DefaultBody));
_locals.UndefineLocal(temp, _instructions.Count);
}
private void CompileIntSwitchExpression<T>(SwitchExpression node) where T : notnull
{
LabelInfo end = DefineLabel(node: null);
bool hasValue = node.Type != typeof(void);
Compile(node.SwitchValue);
var caseDict = new Dictionary<T, int>();
int switchIndex = _instructions.Count;
_instructions.EmitIntSwitch(caseDict);
if (node.DefaultBody != null)
{
Compile(node.DefaultBody, !hasValue);
}
else
{
Debug.Assert(!hasValue);
}
_instructions.EmitBranch(end.GetLabel(this), false, hasValue);
for (int i = 0; i < node.Cases.Count; i++)
{
SwitchCase switchCase = node.Cases[i];
int caseOffset = _instructions.Count - switchIndex;
foreach (ConstantExpression testValue in switchCase.TestValues)
{
var key = (T)testValue.Value!;
caseDict.TryAdd(key, caseOffset);
}
Compile(switchCase.Body, !hasValue);
if (i < node.Cases.Count - 1)
{
_instructions.EmitBranch(end.GetLabel(this), false, hasValue);
}
}
_instructions.MarkLabel(end.GetLabel(this));
}
private void CompileStringSwitchExpression(SwitchExpression node)
{
LabelInfo end = DefineLabel(node: null);
bool hasValue = node.Type != typeof(void);
Compile(node.SwitchValue);
var caseDict = new Dictionary<string, int>();
int switchIndex = _instructions.Count;
// by default same as default
var nullCase = new StrongBox<int>(1);
_instructions.EmitStringSwitch(caseDict, nullCase);
if (node.DefaultBody != null)
{
Compile(node.DefaultBody, !hasValue);
}
else
{
Debug.Assert(!hasValue);
}
_instructions.EmitBranch(end.GetLabel(this), false, hasValue);
for (int i = 0; i < node.Cases.Count; i++)
{
SwitchCase switchCase = node.Cases[i];
int caseOffset = _instructions.Count - switchIndex;
foreach (ConstantExpression testValue in switchCase.TestValues)
{
var key = (string?)testValue.Value;
if (key == null)
{
if (nullCase.Value == 1)
{
nullCase.Value = caseOffset;
}
}
else
{
caseDict.TryAdd(key, caseOffset);
}
}
Compile(switchCase.Body, !hasValue);
if (i < node.Cases.Count - 1)
{
_instructions.EmitBranch(end.GetLabel(this), false, hasValue);
}
}
_instructions.MarkLabel(end.GetLabel(this));
}
private void CompileLabelExpression(Expression expr)
{
var node = (LabelExpression)expr;
// If we're an immediate child of a block, our label will already
// be defined. If not, we need to define our own block so this
// label isn't exposed except to its own child expression.
LabelInfo? label = null;
if (_labelBlock.Kind == LabelScopeKind.Block)
{
_labelBlock.TryGetLabelInfo(node.Target, out label);
// We're in a block but didn't find our label, try switch
if (label == null && _labelBlock.Parent!.Kind == LabelScopeKind.Switch)
{
_labelBlock.Parent.TryGetLabelInfo(node.Target, out label);
}
// if we're in a switch or block, we should've found the label
Debug.Assert(label != null);
}
label ??= DefineLabel(node.Target);
if (node.DefaultValue != null)
{
if (node.Target.Type == typeof(void))
{
CompileAsVoid(node.DefaultValue);
}
else
{
Compile(node.DefaultValue);
}
}
_instructions.MarkLabel(label.GetLabel(this));
}
private void CompileGotoExpression(Expression expr)
{
var node = (GotoExpression)expr;
LabelInfo labelInfo = ReferenceLabel(node.Target);
if (node.Value != null)
{
Compile(node.Value);
}
_instructions.EmitGoto(labelInfo.GetLabel(this),
node.Type != typeof(void),
node.Value != null && node.Value.Type != typeof(void),
node.Target.Type != typeof(void));
}
private void PushLabelBlock(LabelScopeKind type)
{
_labelBlock = new LabelScopeInfo(_labelBlock, type);
}
private void PopLabelBlock(LabelScopeKind kind)
{
Debug.Assert(_labelBlock != null && _labelBlock.Kind == kind);
_labelBlock = _labelBlock.Parent!;
}
private LabelInfo EnsureLabel(LabelTarget node)
{
if (!_treeLabels.TryGetValue(node, out LabelInfo? result))
{
_treeLabels[node] = result = new LabelInfo(node);
}
return result;
}
private LabelInfo ReferenceLabel(LabelTarget node)
{
LabelInfo result = EnsureLabel(node);
result.Reference(_labelBlock);
return result;
}
private LabelInfo DefineLabel(LabelTarget? node)
{
if (node == null)
{
return new LabelInfo(null);
}
LabelInfo result = EnsureLabel(node);
result.Define(_labelBlock);
return result;
}
private bool TryPushLabelBlock(Expression node)
{
// Anything that is "statement-like" -- e.g. has no associated
// stack state can be jumped into, with the exception of try-blocks
// We indicate this by a "Block"
//
// Otherwise, we push an "Expression" to indicate that it can't be
// jumped into
switch (node.NodeType)
{
default:
if (_labelBlock.Kind != LabelScopeKind.Expression)
{
PushLabelBlock(LabelScopeKind.Expression);
return true;
}
return false;
case ExpressionType.Label:
// LabelExpression is a bit special, if it's directly in a
// block it becomes associate with the block's scope. Same
// thing if it's in a switch case body.
if (_labelBlock.Kind == LabelScopeKind.Block)
{
LabelTarget label = ((LabelExpression)node).Target;
if (_labelBlock.ContainsTarget(label))
{
return false;
}
if (_labelBlock.Parent!.Kind == LabelScopeKind.Switch &&
_labelBlock.Parent.ContainsTarget(label))
{
return false;
}
}
PushLabelBlock(LabelScopeKind.Statement);
return true;
case ExpressionType.Block:
PushLabelBlock(LabelScopeKind.Block);
// Labels defined immediately in the block are valid for
// the whole block.
if (_labelBlock.Parent!.Kind != LabelScopeKind.Switch)
{
DefineBlockLabels(node);
}
return true;
case ExpressionType.Switch:
PushLabelBlock(LabelScopeKind.Switch);
// Define labels inside of the switch cases so they are in
// scope for the whole switch. This allows "goto case" and
// "goto default" to be considered as local jumps.
var @switch = (SwitchExpression)node;
foreach (SwitchCase c in @switch.Cases)
{
DefineBlockLabels(c.Body);
}
DefineBlockLabels(@switch.DefaultBody);
return true;
// Remove this when Convert(Void) goes away.
case ExpressionType.Convert:
if (node.Type != typeof(void))
{
// treat it as an expression
goto default;
}
PushLabelBlock(LabelScopeKind.Statement);
return true;
case ExpressionType.Conditional:
case ExpressionType.Loop:
case ExpressionType.Goto:
PushLabelBlock(LabelScopeKind.Statement);
return true;
}
}
private void DefineBlockLabels(Expression? node)
{
var block = node as BlockExpression;
if (block == null)
{
return;
}
for (int i = 0, n = block.Expressions.Count; i < n; i++)
{
Expression e = block.Expressions[i];
var label = e as LabelExpression;
if (label != null)
{
DefineLabel(label.Target);
}
}
}
private void CheckRethrow()
{
// Rethrow is only valid inside a catch.
for (LabelScopeInfo? j = _labelBlock; j != null; j = j.Parent)
{
if (j.Kind == LabelScopeKind.Catch)
{
return;
}
else if (j.Kind == LabelScopeKind.Finally)
{
// Rethrow from inside finally is not verifiable
break;
}
}
throw Error.RethrowRequiresCatch();
}
private void CompileThrowUnaryExpression(Expression expr, bool asVoid)
{
var node = (UnaryExpression)expr;
if (node.Operand == null)
{
CheckRethrow();
CompileParameterExpression(_exceptionForRethrowStack.Peek());
if (asVoid)
{
_instructions.EmitRethrowVoid();
}
else
{
_instructions.EmitRethrow();
}
}
else
{
Compile(node.Operand);
if (asVoid)
{
_instructions.EmitThrowVoid();
}
else
{
_instructions.EmitThrow();
}
}
}
private void CompileTryExpression(Expression expr)
{
var node = (TryExpression)expr;
if (node.Fault != null)
{
CompileTryFaultExpression(node);
}
else
{
BranchLabel end = _instructions.MakeLabel();
BranchLabel gotoEnd = _instructions.MakeLabel();
int tryStart = _instructions.Count;
BranchLabel? startOfFinally = null;
if (node.Finally != null)
{
startOfFinally = _instructions.MakeLabel();
_instructions.EmitEnterTryFinally(startOfFinally);
}
else
{
_instructions.EmitEnterTryCatch();
}
List<ExceptionHandler>? exHandlers = null;
var enterTryInstr = _instructions.GetInstruction(tryStart) as EnterTryCatchFinallyInstruction;
Debug.Assert(enterTryInstr != null);
PushLabelBlock(LabelScopeKind.Try);
bool hasValue = node.Type != typeof(void);
Compile(node.Body, !hasValue);
int tryEnd = _instructions.Count;
// handlers jump here:
_instructions.MarkLabel(gotoEnd);
_instructions.EmitGoto(end, hasValue, hasValue, hasValue);
// keep the result on the stack:
if (node.Handlers.Count > 0)
{
exHandlers = new List<ExceptionHandler>();
foreach (CatchBlock handler in node.Handlers)
{
ParameterExpression parameter = handler.Variable ?? Expression.Parameter(handler.Test);
LocalDefinition local = _locals.DefineLocal(parameter, _instructions.Count);
_exceptionForRethrowStack.Push(parameter);
ExceptionFilter? filter = null;
if (handler.Filter != null)
{
PushLabelBlock(LabelScopeKind.Filter);
_instructions.EmitEnterExceptionFilter();
// at this point the stack balance is prepared for the hidden exception variable:
int filterLabel = _instructions.MarkRuntimeLabel();
int filterStart = _instructions.Count;
CompileSetVariable(parameter, isVoid: true);
Compile(handler.Filter);
CompileGetVariable(parameter);
filter = new ExceptionFilter(filterLabel, filterStart, _instructions.Count);
// keep the value of the body on the stack:
_instructions.EmitLeaveExceptionFilter();
PopLabelBlock(LabelScopeKind.Filter);
}
PushLabelBlock(LabelScopeKind.Catch);
// add a stack balancing nop instruction (exception handling pushes the current exception):
if (hasValue)
{
_instructions.EmitEnterExceptionHandlerNonVoid();
}
else
{
_instructions.EmitEnterExceptionHandlerVoid();
}
// at this point the stack balance is prepared for the hidden exception variable:
int handlerLabel = _instructions.MarkRuntimeLabel();
int handlerStart = _instructions.Count;
CompileSetVariable(parameter, isVoid: true);
Compile(handler.Body, !hasValue);
_exceptionForRethrowStack.Pop();
// keep the value of the body on the stack:
_instructions.EmitLeaveExceptionHandler(hasValue, gotoEnd);
exHandlers.Add(new ExceptionHandler(handlerLabel, handlerStart, _instructions.Count, handler.Test, filter));
PopLabelBlock(LabelScopeKind.Catch);
_locals.UndefineLocal(local, _instructions.Count);
}
}
if (node.Finally != null)
{
Debug.Assert(startOfFinally != null);
PushLabelBlock(LabelScopeKind.Finally);
_instructions.MarkLabel(startOfFinally);
_instructions.EmitEnterFinally(startOfFinally);
CompileAsVoid(node.Finally);
_instructions.EmitLeaveFinally();
enterTryInstr.SetTryHandler(
new TryCatchFinallyHandler(tryStart, tryEnd, gotoEnd.TargetIndex,
startOfFinally.TargetIndex, _instructions.Count,
exHandlers?.ToArray()));
PopLabelBlock(LabelScopeKind.Finally);
}
else
{
Debug.Assert(exHandlers != null);
enterTryInstr.SetTryHandler(
new TryCatchFinallyHandler(tryStart, tryEnd, gotoEnd.TargetIndex, exHandlers.ToArray()));
}
_instructions.MarkLabel(end);
PopLabelBlock(LabelScopeKind.Try);
}
}
private void CompileTryFaultExpression(TryExpression expr)
{
Debug.Assert(expr.Finally == null);
Debug.Assert(expr.Handlers.Count == 0);
// Mark where we begin.
int tryStart = _instructions.Count;
BranchLabel end = _instructions.MakeLabel();
EnterTryFaultInstruction enterTryInstr = _instructions.EmitEnterTryFault(end);
Debug.Assert(enterTryInstr == _instructions.GetInstruction(tryStart));
// Emit the try block.
PushLabelBlock(LabelScopeKind.Try);
bool hasValue = expr.Type != typeof(void);
Compile(expr.Body, !hasValue);
int tryEnd = _instructions.Count;
// Jump out of the try block to the end of the finally. If we got
// This far, then the fault block shouldn't be run.
_instructions.EmitGoto(end, hasValue, hasValue, hasValue);
// Emit the fault block. The scope kind used is the same as for finally
// blocks, which matches the Compiler.LambdaCompiler.EmitTryExpression approach.
PushLabelBlock(LabelScopeKind.Finally);
BranchLabel startOfFault = _instructions.MakeLabel();
_instructions.MarkLabel(startOfFault);
_instructions.EmitEnterFault(startOfFault);
CompileAsVoid(expr.Fault!);
_instructions.EmitLeaveFault();
enterTryInstr.SetTryHandler(new TryFaultHandler(tryStart, tryEnd, startOfFault.TargetIndex, _instructions.Count));
PopLabelBlock(LabelScopeKind.Finally);
PopLabelBlock(LabelScopeKind.Try);
_instructions.MarkLabel(end);
}
private void CompileMethodCallExpression(Expression expr)
{
var node = (MethodCallExpression)expr;
CompileMethodCallExpression(node.Object!, node.Method, node);
}
private void CompileMethodCallExpression(Expression @object, MethodInfo method, IArgumentProvider arguments)
{
ParameterInfo[] parameters = method.GetParametersCached();
// TODO: Support pass by reference.
List<ByRefUpdater>? updaters = null;
if (!method.IsStatic)
{
ByRefUpdater? updater = CompileAddress(@object, -1);
if (updater != null)
{
updaters = new List<ByRefUpdater>() { updater };
}
}
Debug.Assert(parameters.Length == arguments.ArgumentCount);
for (int i = 0, n = arguments.ArgumentCount; i < n; i++)
{
Expression arg = arguments.GetArgument(i);
// byref calls leave out values on the stack, we use a callback
// to emit the code which processes each value left on the stack.
if (parameters[i].ParameterType.IsByRef)
{
ByRefUpdater? updater = CompileAddress(arg, i);
if (updater != null)
{
updaters ??= new List<ByRefUpdater>();
updaters.Add(updater);
}
}
else
{
Compile(arg);
}
}
if (!method.IsStatic &&
@object.Type.IsNullableType())
{
// reflection doesn't let us call methods on Nullable<T> when the value
// is null... so we get to special case those methods!
_instructions.EmitNullableCall(method, parameters);
}
else
{
if (updaters == null)
{
_instructions.EmitCall(method, parameters);
}
else
{
_instructions.EmitByRefCall(method, parameters, updaters.ToArray());
foreach (ByRefUpdater updater in updaters)
{
updater.UndefineTemps(_instructions, _locals);
}
}
}
}
private ByRefUpdater CompileArrayIndexAddress(Expression array, Expression index, int argumentIndex)
{
LocalDefinition left = _locals.DefineLocal(Expression.Parameter(array.Type, nameof(array)), _instructions.Count);
LocalDefinition right = _locals.DefineLocal(Expression.Parameter(index.Type, nameof(index)), _instructions.Count);
Compile(array);
_instructions.EmitStoreLocal(left.Index);
Compile(index);
_instructions.EmitStoreLocal(right.Index);
_instructions.EmitLoadLocal(left.Index);
_instructions.EmitLoadLocal(right.Index);
_instructions.EmitGetArrayItem();
return new ArrayByRefUpdater(left, right, argumentIndex);
}
private void EmitThisForMethodCall(Expression node)
{
CompileAddress(node, -1);
}
private static bool ShouldWritebackNode(Expression node)
{
if (node.Type.IsValueType)
{
switch (node.NodeType)
{
case ExpressionType.Parameter:
case ExpressionType.Call:
case ExpressionType.ArrayIndex:
return true;
case ExpressionType.Index:
return ((IndexExpression)node).Object!.Type.IsArray;
case ExpressionType.MemberAccess:
return ((MemberExpression)node).Member is FieldInfo;
// ExpressionType.Unbox does have the behaviour write-back is used to simulate, but
// it doesn't need explicit write-back to produce it, so include it in the default
// false cases.
}
}
return false;
}
/// <summary>
/// Emits the address of the specified node.
/// </summary>
private ByRefUpdater? CompileAddress(Expression node, int index)
{
if (index != -1 || ShouldWritebackNode(node))
{
switch (node.NodeType)
{
case ExpressionType.Parameter:
LoadLocalNoValueTypeCopy((ParameterExpression)node);
return new ParameterByRefUpdater(ResolveLocal((ParameterExpression)node), index);
case ExpressionType.ArrayIndex:
var array = (BinaryExpression)node;
return CompileArrayIndexAddress(array.Left, array.Right, index);
case ExpressionType.Index:
var indexNode = (IndexExpression)node;
if (/*!TypeUtils.AreEquivalent(type, node.Type) || */indexNode.Indexer != null)
{
LocalDefinition? objTmp = null;
if (indexNode.Object != null)
{
objTmp = _locals.DefineLocal(Expression.Parameter(indexNode.Object.Type), _instructions.Count);
EmitThisForMethodCall(indexNode.Object);
_instructions.EmitDup();
_instructions.EmitStoreLocal(objTmp.GetValueOrDefault().Index);
}
int count = indexNode.ArgumentCount;
var indexLocals = new LocalDefinition[count];
for (int i = 0; i < count; i++)
{
Expression arg = indexNode.GetArgument(i);
Compile(arg);
LocalDefinition argTmp = _locals.DefineLocal(Expression.Parameter(arg.Type), _instructions.Count);
_instructions.EmitDup();
_instructions.EmitStoreLocal(argTmp.Index);
indexLocals[i] = argTmp;
}
EmitIndexGet(indexNode);
return new IndexMethodByRefUpdater(objTmp, indexLocals, indexNode.Indexer.GetSetMethod()!, index);
}
else if (indexNode.ArgumentCount == 1)
{
return CompileArrayIndexAddress(indexNode.Object!, indexNode.GetArgument(0), index);
}
else
{
return CompileMultiDimArrayAccess(indexNode.Object!, indexNode, index);
}
case ExpressionType.MemberAccess:
var member = (MemberExpression)node;
LocalDefinition? memberTemp = null;
if (member.Expression != null)
{
memberTemp = _locals.DefineLocal(Expression.Parameter(member.Expression.Type, "member"), _instructions.Count);
EmitThisForMethodCall(member.Expression);
_instructions.EmitDup();
_instructions.EmitStoreLocal(memberTemp.GetValueOrDefault().Index);
}
var field = member.Member as FieldInfo;
if (field != null)
{
_instructions.EmitLoadField(field);
if (!field.IsLiteral && !field.IsInitOnly)
{
return new FieldByRefUpdater(memberTemp, field, index);
}
return null;
}
Debug.Assert(member.Member is PropertyInfo);
var property = (PropertyInfo)member.Member;
_instructions.EmitCall(property.GetGetMethod(nonPublic: true)!);
if (property.CanWrite)
{
return new PropertyByRefUpdater(memberTemp, property, index);
}
return null;
case ExpressionType.Call:
// An array index of a multi-dimensional array is represented by a call to Array.Get,
// rather than having its own array-access node. This means that when we are trying to
// get the address of a member of a multi-dimensional array, we'll be trying to
// get the address of a Get method, and it will fail to do so. Instead, detect
// this situation and replace it with a call to the Address method.
var call = (MethodCallExpression)node;
if (!call.Method.IsStatic &&
call.Object!.Type.IsArray &&
call.Method == TypeUtils.GetArrayGetMethod(call.Object.Type))
{
return CompileMultiDimArrayAccess(
call.Object,
call,
index
);
}
break;
}
}
// Includes Unbox case as it doesn't need explicit write-back.
Compile(node);
return null;
}
private ByRefUpdater CompileMultiDimArrayAccess(Expression array, IArgumentProvider arguments, int index)
{
Compile(array);
LocalDefinition objTmp = _locals.DefineLocal(Expression.Parameter(array.Type), _instructions.Count);
_instructions.EmitDup();
_instructions.EmitStoreLocal(objTmp.Index);
int count = arguments.ArgumentCount;
var indexLocals = new LocalDefinition[count];
for (int i = 0; i < count; i++)
{
Expression arg = arguments.GetArgument(i);
Compile(arg);
LocalDefinition argTmp = _locals.DefineLocal(Expression.Parameter(arg.Type), _instructions.Count);
_instructions.EmitDup();
_instructions.EmitStoreLocal(argTmp.Index);
indexLocals[i] = argTmp;
}
_instructions.EmitCall(TypeUtils.GetArrayGetMethod(array.Type));
return new IndexMethodByRefUpdater(objTmp, indexLocals, TypeUtils.GetArraySetMethod(array.Type), index);
}
private void CompileNewExpression(Expression expr)
{
var node = (NewExpression)expr;
if (node.Constructor != null)
{
if (node.Constructor.DeclaringType!.IsAbstract)
throw Error.NonAbstractConstructorRequired();
ParameterInfo[] parameters = node.Constructor.GetParametersCached();
List<ByRefUpdater>? updaters = null;
for (int i = 0; i < parameters.Length; i++)
{
Expression arg = node.GetArgument(i);
if (parameters[i].ParameterType.IsByRef)
{
ByRefUpdater? updater = CompileAddress(arg, i);
if (updater != null)
{
updaters ??= new List<ByRefUpdater>();
updaters.Add(updater);
}
}
else
{
Compile(arg);
}
}
if (updaters != null)
{
_instructions.EmitByRefNew(node.Constructor, parameters, updaters.ToArray());
}
else
{
_instructions.EmitNew(node.Constructor, parameters);
}
}
else
{
Type type = node.Type;
Debug.Assert(type.IsValueType);
if (type.IsNullableType())
{
_instructions.EmitLoad(value: null);
}
else
{
_instructions.EmitDefaultValue(type);
}
}
}
private void CompileMemberExpression(Expression expr)
{
var node = (MemberExpression)expr;
CompileMember(node.Expression, node.Member, forBinding: false);
}
private void CompileMember(Expression? from, MemberInfo member, bool forBinding)
{
if (member is FieldInfo fi)
{
if (fi.IsLiteral)
{
Debug.Assert(!forBinding);
_instructions.EmitLoad(fi.GetValue(obj: null), fi.FieldType);
}
else if (fi.IsStatic)
{
if (forBinding)
{
throw Error.InvalidProgram();
}
if (fi.IsInitOnly)
{
_instructions.EmitLoad(fi.GetValue(obj: null), fi.FieldType);
}
else
{
_instructions.EmitLoadField(fi);
}
}
else
{
if (from != null)
{
EmitThisForMethodCall(from);
}
_instructions.EmitLoadField(fi);
}
}
else
{
// MemberExpression can use either FieldInfo or PropertyInfo - other types derived from MemberInfo are not permitted
var pi = (PropertyInfo)member;
if (pi != null)
{
MethodInfo method = pi.GetGetMethod(nonPublic: true)!;
if (forBinding && method.IsStatic)
{
throw Error.InvalidProgram();
}
if (from != null)
{
EmitThisForMethodCall(from);
}
if (!method.IsStatic &&
(from != null && from.Type.IsNullableType()))
{
// reflection doesn't let us call methods on Nullable<T> when the value
// is null... so we get to special case those methods!
_instructions.EmitNullableCall(method, Array.Empty<ParameterInfo>());
}
else
{
_instructions.EmitCall(method);
}
}
}
}
[UnconditionalSuppressMessage("DynamicDependency", "IL3050",
Justification = "NewArrayExpression has RequiresDynamicCode, so the only way to get here is by already "
+ "seeing a warning.")]
private void CompileNewArrayExpression(Expression expr)
{
Debug.Assert(typeof(NewArrayExpression).GetCustomAttribute<RequiresDynamicCodeAttribute>() is not null);
var node = (NewArrayExpression)expr;
foreach (Expression arg in node.Expressions)
{
Compile(arg);
}
Type elementType = node.Type.GetElementType()!;
int rank = node.Expressions.Count;
if (node.NodeType == ExpressionType.NewArrayInit)
{
_instructions.EmitNewArrayInit(elementType, rank);
}
else
{
Debug.Assert(node.NodeType == ExpressionType.NewArrayBounds);
if (rank == 1)
{
_instructions.EmitNewArray(elementType);
}
else
{
_instructions.EmitNewArrayBounds(elementType, rank);
}
}
}
private void CompileDebugInfoExpression(Expression expr)
{
var node = (DebugInfoExpression)expr;
int start = _instructions.Count;
var info = new DebugInfo()
{
Index = start,
FileName = node.Document.FileName,
StartLine = node.StartLine,
EndLine = node.EndLine,
IsClear = node.IsClear
};
_debugInfos.Add(info);
}
private void CompileRuntimeVariablesExpression(Expression expr)
{
// Generates IRuntimeVariables for all requested variables
var node = (RuntimeVariablesExpression)expr;
foreach (ParameterExpression variable in node.Variables)
{
EnsureAvailableForClosure(variable);
CompileGetBoxedVariable(variable);
}
_instructions.EmitNewRuntimeVariables(node.Variables.Count);
}
private void CompileLambdaExpression(Expression expr)
{
var node = (LambdaExpression)expr;
var compiler = new LightCompiler(this);
LightDelegateCreator creator = compiler.CompileTop(node);
if (compiler._locals.ClosureVariables != null)
{
foreach (ParameterExpression variable in compiler._locals.ClosureVariables.Keys)
{
EnsureAvailableForClosure(variable);
CompileGetBoxedVariable(variable);
}
}
_instructions.EmitCreateDelegate(creator);
}
private void CompileCoalesceBinaryExpression(Expression expr)
{
var node = (BinaryExpression)expr;
bool hasConversion = node.Conversion != null;
bool hasImplicitConversion = false;
if (!hasConversion && node.Left.Type.IsNullableType())
{
// reference types don't need additional conversions (the interpreter operates on Object
// anyway); non-nullable value types can't occur on the left side; all that's left is
// nullable value types with implicit (numeric) conversions which are allowed by Coalesce
// factory methods
Type typeToCompare = node.Left.Type;
if (!node.Type.IsNullableType())
{
typeToCompare = typeToCompare.GetNonNullableType();
}
if (!TypeUtils.AreEquivalent(node.Type, typeToCompare))
{
hasImplicitConversion = true;
hasConversion = true;
}
}
BranchLabel leftNotNull = _instructions.MakeLabel();
BranchLabel? end = null;
Compile(node.Left);
_instructions.EmitCoalescingBranch(leftNotNull);
_instructions.EmitPop();
Compile(node.Right);
if (hasConversion)
{
// skip over conversion on RHS
end = _instructions.MakeLabel();
_instructions.EmitBranch(end);
}
else if (node.Right.Type.IsValueType && !TypeUtils.AreEquivalent(node.Type, node.Right.Type))
{
// The right hand side may need to be widened to either the left hand side's type
// if the right hand side is nullable, or the left hand side's underlying type otherwise
CompileConvertToType(node.Right.Type, node.Type, isChecked: true, isLiftedToNull: node.Type.IsNullableType());
}
_instructions.MarkLabel(leftNotNull);
if (node.Conversion != null)
{
ParameterExpression temp = Expression.Parameter(node.Left.Type, "temp");
LocalDefinition local = _locals.DefineLocal(temp, _instructions.Count);
_instructions.EmitStoreLocal(local.Index);
CompileMethodCallExpression(
Expression.Call(node.Conversion, node.Conversion.Type.GetInvokeMethod(), new[] { temp })
);
_locals.UndefineLocal(local, _instructions.Count);
}
else if (hasImplicitConversion)
{
Type nnLeftType = node.Left.Type.GetNonNullableType();
CompileConvertToType(nnLeftType, node.Type, isChecked: true, isLiftedToNull: false);
}
if (hasConversion)
{
_instructions.MarkLabel(end!);
}
}
private void CompileInvocationExpression(Expression expr)
{
var node = (InvocationExpression)expr;
if (typeof(LambdaExpression).IsAssignableFrom(node.Expression.Type))
{
MethodInfo compMethod = LambdaExpression.GetCompileMethod(node.Expression.Type);
CompileMethodCallExpression(
Expression.Call(
node.Expression,
compMethod
),
compMethod.ReturnType.GetInvokeMethod(),
node
);
}
else
{
CompileMethodCallExpression(
node.Expression, node.Expression.Type.GetInvokeMethod(), node
);
}
}
private void CompileListInitExpression(Expression expr)
{
var node = (ListInitExpression)expr;
EmitThisForMethodCall(node.NewExpression);
ReadOnlyCollection<ElementInit> initializers = node.Initializers;
CompileListInit(initializers);
}
private void CompileListInit(ReadOnlyCollection<ElementInit> initializers)
{
for (int i = 0; i < initializers.Count; i++)
{
ElementInit initializer = initializers[i];
_instructions.EmitDup();
foreach (Expression arg in initializer.Arguments)
{
Compile(arg);
}
MethodInfo add = initializer.AddMethod;
_instructions.EmitCall(add);
if (add.ReturnType != typeof(void))
_instructions.EmitPop();
}
}
private void CompileMemberInitExpression(Expression expr)
{
var node = (MemberInitExpression)expr;
EmitThisForMethodCall(node.NewExpression);
CompileMemberInit(node.Bindings);
}
private void CompileMemberInit(ReadOnlyCollection<MemberBinding> bindings)
{
foreach (MemberBinding binding in bindings)
{
switch (binding.BindingType)
{
case MemberBindingType.Assignment:
_instructions.EmitDup();
CompileMemberAssignment(
true,
((MemberAssignment)binding).Member,
((MemberAssignment)binding).Expression,
forBinding: true
);
break;
case MemberBindingType.ListBinding:
var memberList = (MemberListBinding)binding;
_instructions.EmitDup();
CompileMember(null, memberList.Member, forBinding: true);
CompileListInit(memberList.Initializers);
_instructions.EmitPop();
break;
case MemberBindingType.MemberBinding:
var memberMember = (MemberMemberBinding)binding;
_instructions.EmitDup();
Type type = GetMemberType(memberMember.Member);
if (memberMember.Member is PropertyInfo && type.IsValueType)
{
throw Error.CannotAutoInitializeValueTypeMemberThroughProperty(memberMember.Bindings);
}
CompileMember(null, memberMember.Member, forBinding: true);
CompileMemberInit(memberMember.Bindings);
_instructions.EmitPop();
break;
}
}
}
private static Type GetMemberType(MemberInfo member)
{
var fi = member as FieldInfo;
if (fi != null) return fi.FieldType;
var pi = member as PropertyInfo;
if (pi != null) return pi.PropertyType;
throw new InvalidOperationException("MemberNotFieldOrProperty");
}
private void CompileQuoteUnaryExpression(Expression expr)
{
var unary = (UnaryExpression)expr;
var visitor = new QuoteVisitor();
visitor.Visit(unary.Operand);
var mapping = new Dictionary<ParameterExpression, LocalVariable>();
foreach (ParameterExpression local in visitor._hoistedParameters)
{
EnsureAvailableForClosure(local);
mapping[local] = ResolveLocal(local);
}
_instructions.Emit(new QuoteInstruction(unary.Operand, mapping.Count > 0 ? mapping : null));
}
private sealed class QuoteVisitor : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, int> _definedParameters = new Dictionary<ParameterExpression, int>();
public readonly HashSet<ParameterExpression> _hoistedParameters = new HashSet<ParameterExpression>();
protected internal override Expression VisitParameter(ParameterExpression node)
{
if (!_definedParameters.ContainsKey(node))
{
_hoistedParameters.Add(node);
}
return node;
}
protected internal override Expression VisitBlock(BlockExpression node)
{
PushParameters(node.Variables);
base.VisitBlock(node);
PopParameters(node.Variables);
return node;
}
protected override CatchBlock VisitCatchBlock(CatchBlock node)
{
if (node.Variable != null)
{
PushParameters(new[] { node.Variable });
}
Visit(node.Body);
Visit(node.Filter);
if (node.Variable != null)
{
PopParameters(new[] { node.Variable });
}
return node;
}
protected internal override Expression VisitLambda<T>(Expression<T> node)
{
IEnumerable<ParameterExpression> parameters = Array.Empty<ParameterExpression>();
int count = node.ParameterCount;
if (count > 0)
{
var parameterList = new List<ParameterExpression>(count);
for (int i = 0; i < count; i++)
{
parameterList.Add(node.GetParameter(i));
}
parameters = parameterList;
}
PushParameters(parameters);
base.VisitLambda(node);
PopParameters(parameters);
return node;
}
private void PushParameters(IEnumerable<ParameterExpression> parameters)
{
foreach (ParameterExpression param in parameters)
{
int count;
if (_definedParameters.TryGetValue(param, out count))
{
_definedParameters[param] = count + 1;
}
else
{
_definedParameters[param] = 1;
}
}
}
private void PopParameters(IEnumerable<ParameterExpression> parameters)
{
foreach (ParameterExpression param in parameters)
{
int count = _definedParameters[param];
if (count == 0)
{
_definedParameters.Remove(param);
}
else
{
_definedParameters[param] = count - 1;
}
}
}
}
private void CompileUnboxUnaryExpression(Expression expr)
{
var node = (UnaryExpression)expr;
Compile(node.Operand);
if (node.Type.IsValueType && !node.Type.IsNullableType())
{
_instructions.Emit(NullCheckInstruction.Instance);
}
}
private void CompileTypeEqualExpression(Expression expr)
{
Debug.Assert(expr.NodeType == ExpressionType.TypeEqual);
var node = (TypeBinaryExpression)expr;
Compile(node.Expression);
if (node.Expression.Type == typeof(void))
{
_instructions.EmitLoad(node.TypeOperand == typeof(void), typeof(bool));
}
else
{
_instructions.EmitLoad(node.TypeOperand.GetNonNullableType());
_instructions.EmitTypeEquals();
}
}
private void CompileTypeAsExpression(UnaryExpression node)
{
Compile(node.Operand);
_instructions.EmitTypeAs(node.Type);
}
private void CompileTypeIsExpression(Expression expr)
{
Debug.Assert(expr.NodeType == ExpressionType.TypeIs);
var node = (TypeBinaryExpression)expr;
AnalyzeTypeIsResult result = ConstantCheck.AnalyzeTypeIs(node);
Compile(node.Expression);
switch (result)
{
case AnalyzeTypeIsResult.KnownTrue:
case AnalyzeTypeIsResult.KnownFalse:
// Result is known statically, so just emit the expression for
// its side effects and return the result
if (node.Expression.Type != typeof(void))
{
_instructions.EmitPop();
}
_instructions.EmitLoad(result == AnalyzeTypeIsResult.KnownTrue);
break;
case AnalyzeTypeIsResult.KnownAssignable:
// Either the value is of the type or it is null
// so emit test for not-null.
_instructions.EmitLoad(null);
_instructions.EmitNotEqual(typeof(object));
break;
default:
if (node.TypeOperand.IsValueType)
{
_instructions.EmitLoad(node.TypeOperand.GetNonNullableType());
_instructions.EmitTypeEquals();
}
else
{
_instructions.EmitTypeIs(node.TypeOperand);
}
break;
}
}
private void Compile(Expression expr, bool asVoid)
{
if (asVoid)
{
CompileAsVoid(expr);
}
else
{
Compile(expr);
}
}
private void CompileAsVoid(Expression expr)
{
bool pushLabelBlock = TryPushLabelBlock(expr);
int startingStackDepth = _instructions.CurrentStackDepth;
switch (expr.NodeType)
{
case ExpressionType.Assign:
CompileAssignBinaryExpression(expr, asVoid: true);
break;
case ExpressionType.Block:
CompileBlockExpression(expr, asVoid: true);
break;
case ExpressionType.Throw:
CompileThrowUnaryExpression(expr, asVoid: true);
break;
case ExpressionType.Constant:
case ExpressionType.Default:
case ExpressionType.Parameter:
// no-op
break;
default:
CompileNoLabelPush(expr);
if (expr.Type != typeof(void))
{
_instructions.EmitPop();
}
break;
}
Debug.Assert(_instructions.CurrentStackDepth == startingStackDepth);
if (pushLabelBlock)
{
PopLabelBlock(_labelBlock.Kind);
}
}
private void CompileNoLabelPush(Expression expr)
{
// When compiling deep trees, we run the risk of triggering a terminating StackOverflowException,
// so we use the StackGuard utility here to probe for sufficient stack and continue the work on
// another thread when we run out of stack space.
if (!_guard.TryEnterOnCurrentStack())
{
_guard.RunOnEmptyStack((LightCompiler @this, Expression e) => @this.CompileNoLabelPush(e), this, expr);
return;
}
int startingStackDepth = _instructions.CurrentStackDepth;
switch (expr.NodeType)
{
case ExpressionType.Add:
case ExpressionType.AddChecked:
case ExpressionType.And:
case ExpressionType.ArrayIndex:
case ExpressionType.Divide:
case ExpressionType.Equal:
case ExpressionType.ExclusiveOr:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.LeftShift:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.Modulo:
case ExpressionType.Multiply:
case ExpressionType.MultiplyChecked:
case ExpressionType.NotEqual:
case ExpressionType.Or:
case ExpressionType.Power:
case ExpressionType.RightShift:
case ExpressionType.Subtract:
case ExpressionType.SubtractChecked: CompileBinaryExpression(expr); break;
case ExpressionType.AndAlso: CompileAndAlsoBinaryExpression(expr); break;
case ExpressionType.OrElse: CompileOrElseBinaryExpression(expr); break;
case ExpressionType.Coalesce: CompileCoalesceBinaryExpression(expr); break;
case ExpressionType.ArrayLength:
case ExpressionType.Decrement:
case ExpressionType.Increment:
case ExpressionType.IsTrue:
case ExpressionType.IsFalse:
case ExpressionType.Negate:
case ExpressionType.NegateChecked:
case ExpressionType.Not:
case ExpressionType.OnesComplement:
case ExpressionType.TypeAs:
case ExpressionType.UnaryPlus: CompileUnaryExpression(expr); break;
case ExpressionType.Convert:
case ExpressionType.ConvertChecked: CompileConvertUnaryExpression(expr); break;
case ExpressionType.Quote: CompileQuoteUnaryExpression(expr); break;
case ExpressionType.Throw: CompileThrowUnaryExpression(expr, expr.Type == typeof(void)); break;
case ExpressionType.Unbox: CompileUnboxUnaryExpression(expr); break;
case ExpressionType.Call: CompileMethodCallExpression(expr); break;
case ExpressionType.Conditional: CompileConditionalExpression(expr, expr.Type == typeof(void)); break;
case ExpressionType.Constant: CompileConstantExpression(expr); break;
case ExpressionType.Invoke: CompileInvocationExpression(expr); break;
case ExpressionType.Lambda: CompileLambdaExpression(expr); break;
case ExpressionType.ListInit: CompileListInitExpression(expr); break;
case ExpressionType.MemberAccess: CompileMemberExpression(expr); break;
case ExpressionType.MemberInit: CompileMemberInitExpression(expr); break;
case ExpressionType.New: CompileNewExpression(expr); break;
case ExpressionType.NewArrayInit:
case ExpressionType.NewArrayBounds: CompileNewArrayExpression(expr); break;
case ExpressionType.Parameter: CompileParameterExpression(expr); break;
case ExpressionType.TypeIs: CompileTypeIsExpression(expr); break;
case ExpressionType.TypeEqual: CompileTypeEqualExpression(expr); break;
case ExpressionType.Assign: CompileAssignBinaryExpression(expr, expr.Type == typeof(void)); break;
case ExpressionType.Block: CompileBlockExpression(expr, expr.Type == typeof(void)); break;
case ExpressionType.DebugInfo: CompileDebugInfoExpression(expr); break;
case ExpressionType.Default: CompileDefaultExpression(expr); break;
case ExpressionType.Goto: CompileGotoExpression(expr); break;
case ExpressionType.Index: CompileIndexExpression(expr); break;
case ExpressionType.Label: CompileLabelExpression(expr); break;
case ExpressionType.RuntimeVariables: CompileRuntimeVariablesExpression(expr); break;
case ExpressionType.Loop: CompileLoopExpression(expr); break;
case ExpressionType.Switch: CompileSwitchExpression(expr); break;
case ExpressionType.Try: CompileTryExpression(expr); break;
default:
Compile(expr.ReduceAndCheck());
break;
}
Debug.Assert(_instructions.CurrentStackDepth == startingStackDepth + (expr.Type == typeof(void) ? 0 : 1),
$"{_instructions.CurrentStackDepth} vs {startingStackDepth + (expr.Type == typeof(void) ? 0 : 1)} for {expr.NodeType}");
}
private void Compile(Expression expr)
{
bool pushLabelBlock = TryPushLabelBlock(expr);
CompileNoLabelPush(expr);
if (pushLabelBlock)
{
PopLabelBlock(_labelBlock.Kind);
}
}
}
internal abstract class ByRefUpdater
{
public readonly int ArgumentIndex;
public ByRefUpdater(int argumentIndex)
{
ArgumentIndex = argumentIndex;
}
public abstract void Update(InterpretedFrame frame, object? value);
public virtual void UndefineTemps(InstructionList instructions, LocalVariables locals)
{
}
}
internal sealed class ParameterByRefUpdater : ByRefUpdater
{
private readonly LocalVariable _parameter;
public ParameterByRefUpdater(LocalVariable parameter, int argumentIndex)
: base(argumentIndex)
{
_parameter = parameter;
}
public override void Update(InterpretedFrame frame, object? value)
{
if (_parameter.InClosure)
{
IStrongBox box = frame.Closure![_parameter.Index];
box.Value = value;
}
else if (_parameter.IsBoxed)
{
var box = (IStrongBox)frame.Data[_parameter.Index]!;
box.Value = value;
}
else
{
frame.Data[_parameter.Index] = value;
}
}
}
internal sealed class ArrayByRefUpdater : ByRefUpdater
{
private readonly LocalDefinition _array, _index;
public ArrayByRefUpdater(LocalDefinition array, LocalDefinition index, int argumentIndex)
: base(argumentIndex)
{
_array = array;
_index = index;
}
public override void Update(InterpretedFrame frame, object? value)
{
object? index = frame.Data[_index.Index];
((Array)frame.Data[_array.Index]!).SetValue(value, (int)index!);
}
public override void UndefineTemps(InstructionList instructions, LocalVariables locals)
{
locals.UndefineLocal(_array, instructions.Count);
locals.UndefineLocal(_index, instructions.Count);
}
}
internal sealed class FieldByRefUpdater : ByRefUpdater
{
private readonly LocalDefinition? _object;
private readonly FieldInfo _field;
public FieldByRefUpdater(LocalDefinition? obj, FieldInfo field, int argumentIndex)
: base(argumentIndex)
{
_object = obj;
_field = field;
}
public override void Update(InterpretedFrame frame, object? value)
{
object? obj = _object == null ? null : frame.Data[_object.GetValueOrDefault().Index];
_field.SetValue(obj, value);
}
public override void UndefineTemps(InstructionList instructions, LocalVariables locals)
{
if (_object != null)
{
locals.UndefineLocal(_object.GetValueOrDefault(), instructions.Count);
}
}
}
internal sealed class PropertyByRefUpdater : ByRefUpdater
{
private readonly LocalDefinition? _object;
private readonly PropertyInfo _property;
public PropertyByRefUpdater(LocalDefinition? obj, PropertyInfo property, int argumentIndex)
: base(argumentIndex)
{
_object = obj;
_property = property;
}
public override void Update(InterpretedFrame frame, object? value)
{
object? obj = _object == null ? null : frame.Data[_object.GetValueOrDefault().Index];
try
{
_property.SetValue(obj, value);
}
catch (TargetInvocationException e)
{
ExceptionHelpers.UnwrapAndRethrow(e);
throw ContractUtils.Unreachable;
}
}
public override void UndefineTemps(InstructionList instructions, LocalVariables locals)
{
if (_object != null)
{
locals.UndefineLocal(_object.GetValueOrDefault(), instructions.Count);
}
}
}
internal sealed class IndexMethodByRefUpdater : ByRefUpdater
{
private readonly MethodInfo _indexer;
private readonly LocalDefinition? _obj;
private readonly LocalDefinition[] _args;
public IndexMethodByRefUpdater(LocalDefinition? obj, LocalDefinition[] args, MethodInfo indexer, int argumentIndex)
: base(argumentIndex)
{
_obj = obj;
_args = args;
_indexer = indexer;
}
public override void Update(InterpretedFrame frame, object? value)
{
var args = new object?[_args.Length + 1];
for (int i = 0; i < args.Length - 1; i++)
{
args[i] = frame.Data[_args[i].Index];
}
args[args.Length - 1] = value;
object? instance = _obj == null ? null : frame.Data[_obj.GetValueOrDefault().Index];
try
{
_indexer.Invoke(instance, args);
}
catch (TargetInvocationException e)
{
ExceptionHelpers.UnwrapAndRethrow(e);
throw ContractUtils.Unreachable;
}
}
public override void UndefineTemps(InstructionList instructions, LocalVariables locals)
{
if (_obj != null)
{
locals.UndefineLocal(_obj.GetValueOrDefault(), instructions.Count);
}
for (int i = 0; i < _args.Length; i++)
{
locals.UndefineLocal(_args[i], instructions.Count);
}
}
}
}
|