/* **************************************************************************** * * Copyright (c) Microsoft Corporation. * * This source code is subject to terms and conditions of the Apache License, Version 2.0. A * copy of the license can be found in the License.html file at the root of this distribution. If * you cannot locate the Apache License, Version 2.0, please send an email to * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound * by the terms of the Apache License, Version 2.0. * * You must not remove this notice, or any other, from this software. * * * ***************************************************************************/ using System; using System.Diagnostics; using System.Dynamic.Utils; using System.Reflection; using System.Reflection.Emit; #if !FEATURE_CORE_DLR namespace Microsoft.Scripting.Ast.Compiler { #else namespace System.Linq.Expressions.Compiler { #endif partial class LambdaCompiler { #region Conditional private void EmitConditionalExpression(Expression expr, CompilationFlags flags) { ConditionalExpression node = (ConditionalExpression)expr; Debug.Assert(node.Test.Type == typeof(bool)); Label labFalse = _ilg.DefineLabel(); EmitExpressionAndBranch(false, node.Test, labFalse); EmitExpressionAsType(node.IfTrue, node.Type, flags); if (NotEmpty(node.IfFalse)) { Label labEnd = _ilg.DefineLabel(); if ((flags & CompilationFlags.EmitAsTailCallMask) == CompilationFlags.EmitAsTail) { // We know the conditional expression is at the end of the lambda, // so it is safe to emit Ret here. _ilg.Emit(OpCodes.Ret); } else { _ilg.Emit(OpCodes.Br, labEnd); } _ilg.MarkLabel(labFalse); EmitExpressionAsType(node.IfFalse, node.Type, flags); _ilg.MarkLabel(labEnd); } else { _ilg.MarkLabel(labFalse); } } /// /// returns true if the expression is not empty, otherwise false. /// private static bool NotEmpty(Expression node) { var empty = node as DefaultExpression; if (empty == null || empty.Type != typeof(void)) { return true; } return false; } /// /// returns true if the expression is NOT empty and is not debug info, /// or a block that contains only insignificant expressions. /// private static bool Significant(Expression node) { var block = node as BlockExpression; if (block != null) { for (int i = 0; i < block.ExpressionCount; i++) { if (Significant(block.GetExpression(i))) { return true; } } return false; } return NotEmpty(node) && !(node is DebugInfoExpression); } #endregion #region Coalesce private void EmitCoalesceBinaryExpression(Expression expr) { BinaryExpression b = (BinaryExpression)expr; Debug.Assert(b.Method == null); if (TypeUtils.IsNullableType(b.Left.Type)) { EmitNullableCoalesce(b); } else if (b.Left.Type.IsValueType) { throw Error.CoalesceUsedOnNonNullType(); } else if (b.Conversion != null) { EmitLambdaReferenceCoalesce(b); } else { EmitReferenceCoalesceWithoutConversion(b); } } private void EmitNullableCoalesce(BinaryExpression b) { Debug.Assert(b.Method == null); LocalBuilder loc = GetLocal(b.Left.Type); Label labIfNull = _ilg.DefineLabel(); Label labEnd = _ilg.DefineLabel(); EmitExpression(b.Left); _ilg.Emit(OpCodes.Stloc, loc); _ilg.Emit(OpCodes.Ldloca, loc); _ilg.EmitHasValue(b.Left.Type); _ilg.Emit(OpCodes.Brfalse, labIfNull); Type nnLeftType = TypeUtils.GetNonNullableType(b.Left.Type); if (b.Conversion != null) { Debug.Assert(b.Conversion.Parameters.Count == 1); ParameterExpression p = b.Conversion.Parameters[0]; Debug.Assert(p.Type.IsAssignableFrom(b.Left.Type) || p.Type.IsAssignableFrom(nnLeftType)); // emit the delegate instance EmitLambdaExpression(b.Conversion); // emit argument if (!p.Type.IsAssignableFrom(b.Left.Type)) { _ilg.Emit(OpCodes.Ldloca, loc); _ilg.EmitGetValueOrDefault(b.Left.Type); } else { _ilg.Emit(OpCodes.Ldloc, loc); } // emit call to invoke _ilg.Emit(OpCodes.Callvirt, b.Conversion.Type.GetMethod("Invoke")); } else if (!TypeUtils.AreEquivalent(b.Type, nnLeftType)) { _ilg.Emit(OpCodes.Ldloca, loc); _ilg.EmitGetValueOrDefault(b.Left.Type); _ilg.EmitConvertToType(nnLeftType, b.Type, true); } else { _ilg.Emit(OpCodes.Ldloca, loc); _ilg.EmitGetValueOrDefault(b.Left.Type); } FreeLocal(loc); _ilg.Emit(OpCodes.Br, labEnd); _ilg.MarkLabel(labIfNull); EmitExpression(b.Right); if (!TypeUtils.AreEquivalent(b.Right.Type, b.Type)) { _ilg.EmitConvertToType(b.Right.Type, b.Type, true); } _ilg.MarkLabel(labEnd); } private void EmitLambdaReferenceCoalesce(BinaryExpression b) { LocalBuilder loc = GetLocal(b.Left.Type); Label labEnd = _ilg.DefineLabel(); Label labNotNull = _ilg.DefineLabel(); EmitExpression(b.Left); _ilg.Emit(OpCodes.Dup); _ilg.Emit(OpCodes.Stloc, loc); _ilg.Emit(OpCodes.Ldnull); _ilg.Emit(OpCodes.Ceq); _ilg.Emit(OpCodes.Brfalse, labNotNull); EmitExpression(b.Right); _ilg.Emit(OpCodes.Br, labEnd); // if not null, call conversion _ilg.MarkLabel(labNotNull); Debug.Assert(b.Conversion.Parameters.Count == 1); // emit the delegate instance EmitLambdaExpression(b.Conversion); // emit argument _ilg.Emit(OpCodes.Ldloc, loc); FreeLocal(loc); // emit call to invoke _ilg.Emit(OpCodes.Callvirt, b.Conversion.Type.GetMethod("Invoke")); _ilg.MarkLabel(labEnd); } private void EmitReferenceCoalesceWithoutConversion(BinaryExpression b) { Label labEnd = _ilg.DefineLabel(); Label labCast = _ilg.DefineLabel(); EmitExpression(b.Left); _ilg.Emit(OpCodes.Dup); _ilg.Emit(OpCodes.Ldnull); _ilg.Emit(OpCodes.Ceq); _ilg.Emit(OpCodes.Brfalse, labCast); _ilg.Emit(OpCodes.Pop); EmitExpression(b.Right); if (!TypeUtils.AreEquivalent(b.Right.Type, b.Type)) { if (b.Right.Type.IsValueType) { _ilg.Emit(OpCodes.Box, b.Right.Type); } _ilg.Emit(OpCodes.Castclass, b.Type); } _ilg.Emit(OpCodes.Br_S, labEnd); _ilg.MarkLabel(labCast); if (!TypeUtils.AreEquivalent(b.Left.Type, b.Type)) { Debug.Assert(!b.Left.Type.IsValueType); _ilg.Emit(OpCodes.Castclass, b.Type); } _ilg.MarkLabel(labEnd); } #endregion #region AndAlso private void EmitLiftedAndAlso(BinaryExpression b) { Type type = typeof(bool?); Label labComputeRight = _ilg.DefineLabel(); Label labReturnFalse = _ilg.DefineLabel(); Label labReturnNull = _ilg.DefineLabel(); Label labReturnValue = _ilg.DefineLabel(); Label labExit = _ilg.DefineLabel(); LocalBuilder locLeft = GetLocal(type); LocalBuilder locRight = GetLocal(type); EmitExpression(b.Left); _ilg.Emit(OpCodes.Stloc, locLeft); _ilg.Emit(OpCodes.Ldloca, locLeft); _ilg.EmitHasValue(type); _ilg.Emit(OpCodes.Brfalse, labComputeRight); _ilg.Emit(OpCodes.Ldloca, locLeft); _ilg.EmitGetValueOrDefault(type); _ilg.Emit(OpCodes.Ldc_I4_0); _ilg.Emit(OpCodes.Ceq); _ilg.Emit(OpCodes.Brtrue, labReturnFalse); // compute right _ilg.MarkLabel(labComputeRight); EmitExpression(b.Right); _ilg.Emit(OpCodes.Stloc, locRight); _ilg.Emit(OpCodes.Ldloca, locRight); _ilg.EmitHasValue(type); _ilg.Emit(OpCodes.Brfalse_S, labReturnNull); _ilg.Emit(OpCodes.Ldloca, locRight); _ilg.EmitGetValueOrDefault(type); _ilg.Emit(OpCodes.Ldc_I4_0); _ilg.Emit(OpCodes.Ceq); _ilg.Emit(OpCodes.Brtrue_S, labReturnFalse); // check left for null again _ilg.Emit(OpCodes.Ldloca, locLeft); _ilg.EmitHasValue(type); _ilg.Emit(OpCodes.Brfalse, labReturnNull); // return true _ilg.Emit(OpCodes.Ldc_I4_1); _ilg.Emit(OpCodes.Br_S, labReturnValue); // return false _ilg.MarkLabel(labReturnFalse); _ilg.Emit(OpCodes.Ldc_I4_0); _ilg.Emit(OpCodes.Br_S, labReturnValue); _ilg.MarkLabel(labReturnValue); ConstructorInfo ci = type.GetConstructor(new Type[] { typeof(bool) }); _ilg.Emit(OpCodes.Newobj, ci); _ilg.Emit(OpCodes.Stloc, locLeft); _ilg.Emit(OpCodes.Br, labExit); // return null _ilg.MarkLabel(labReturnNull); _ilg.Emit(OpCodes.Ldloca, locLeft); _ilg.Emit(OpCodes.Initobj, type); _ilg.MarkLabel(labExit); _ilg.Emit(OpCodes.Ldloc, locLeft); FreeLocal(locLeft); FreeLocal(locRight); } private void EmitMethodAndAlso(BinaryExpression b, CompilationFlags flags) { Label labEnd = _ilg.DefineLabel(); EmitExpression(b.Left); _ilg.Emit(OpCodes.Dup); MethodInfo opFalse = TypeUtils.GetBooleanOperator(b.Method.DeclaringType, "op_False"); Debug.Assert(opFalse != null, "factory should check that the method exists"); _ilg.Emit(OpCodes.Call, opFalse); _ilg.Emit(OpCodes.Brtrue, labEnd); //store the value of the left value before emitting b.Right to empty the evaluation stack LocalBuilder locLeft = GetLocal(b.Left.Type); _ilg.Emit(OpCodes.Stloc, locLeft); EmitExpression(b.Right); //store the right value to local LocalBuilder locRight = GetLocal(b.Right.Type); _ilg.Emit(OpCodes.Stloc, locRight); Debug.Assert(b.Method.IsStatic); _ilg.Emit(OpCodes.Ldloc, locLeft); _ilg.Emit(OpCodes.Ldloc, locRight); if ((flags & CompilationFlags.EmitAsTailCallMask) == CompilationFlags.EmitAsTail) { _ilg.Emit(OpCodes.Tailcall); } _ilg.Emit(OpCodes.Call, b.Method); FreeLocal(locLeft); FreeLocal(locRight); _ilg.MarkLabel(labEnd); } private void EmitUnliftedAndAlso(BinaryExpression b) { Label @else = _ilg.DefineLabel(); Label end = _ilg.DefineLabel(); EmitExpressionAndBranch(false, b.Left, @else); EmitExpression(b.Right); _ilg.Emit(OpCodes.Br, end); _ilg.MarkLabel(@else); _ilg.Emit(OpCodes.Ldc_I4_0); _ilg.MarkLabel(end); } private void EmitAndAlsoBinaryExpression(Expression expr, CompilationFlags flags) { BinaryExpression b = (BinaryExpression)expr; if (b.Method != null && !b.IsLiftedLogical) { EmitMethodAndAlso(b, flags); } else if (b.Left.Type == typeof(bool?)) { EmitLiftedAndAlso(b); } else if (b.IsLiftedLogical) { EmitExpression(b.ReduceUserdefinedLifted()); } else { EmitUnliftedAndAlso(b); } } #endregion #region OrElse private void EmitLiftedOrElse(BinaryExpression b) { Type type = typeof(bool?); Label labComputeRight = _ilg.DefineLabel(); Label labReturnTrue = _ilg.DefineLabel(); Label labReturnNull = _ilg.DefineLabel(); Label labReturnValue = _ilg.DefineLabel(); Label labExit = _ilg.DefineLabel(); LocalBuilder locLeft = GetLocal(type); LocalBuilder locRight = GetLocal(type); EmitExpression(b.Left); _ilg.Emit(OpCodes.Stloc, locLeft); _ilg.Emit(OpCodes.Ldloca, locLeft); _ilg.EmitHasValue(type); _ilg.Emit(OpCodes.Brfalse, labComputeRight); _ilg.Emit(OpCodes.Ldloca, locLeft); _ilg.EmitGetValueOrDefault(type); _ilg.Emit(OpCodes.Ldc_I4_0); _ilg.Emit(OpCodes.Ceq); _ilg.Emit(OpCodes.Brfalse, labReturnTrue); // compute right _ilg.MarkLabel(labComputeRight); EmitExpression(b.Right); _ilg.Emit(OpCodes.Stloc, locRight); _ilg.Emit(OpCodes.Ldloca, locRight); _ilg.EmitHasValue(type); _ilg.Emit(OpCodes.Brfalse_S, labReturnNull); _ilg.Emit(OpCodes.Ldloca, locRight); _ilg.EmitGetValueOrDefault(type); _ilg.Emit(OpCodes.Ldc_I4_0); _ilg.Emit(OpCodes.Ceq); _ilg.Emit(OpCodes.Brfalse_S, labReturnTrue); // check left for null again _ilg.Emit(OpCodes.Ldloca, locLeft); _ilg.EmitHasValue(type); _ilg.Emit(OpCodes.Brfalse, labReturnNull); // return false _ilg.Emit(OpCodes.Ldc_I4_0); _ilg.Emit(OpCodes.Br_S, labReturnValue); // return true _ilg.MarkLabel(labReturnTrue); _ilg.Emit(OpCodes.Ldc_I4_1); _ilg.Emit(OpCodes.Br_S, labReturnValue); _ilg.MarkLabel(labReturnValue); ConstructorInfo ci = type.GetConstructor(new Type[] { typeof(bool) }); _ilg.Emit(OpCodes.Newobj, ci); _ilg.Emit(OpCodes.Stloc, locLeft); _ilg.Emit(OpCodes.Br, labExit); // return null _ilg.MarkLabel(labReturnNull); _ilg.Emit(OpCodes.Ldloca, locLeft); _ilg.Emit(OpCodes.Initobj, type); _ilg.MarkLabel(labExit); _ilg.Emit(OpCodes.Ldloc, locLeft); FreeLocal(locLeft); FreeLocal(locRight); } private void EmitUnliftedOrElse(BinaryExpression b) { Label @else = _ilg.DefineLabel(); Label end = _ilg.DefineLabel(); EmitExpressionAndBranch(false, b.Left, @else); _ilg.Emit(OpCodes.Ldc_I4_1); _ilg.Emit(OpCodes.Br, end); _ilg.MarkLabel(@else); EmitExpression(b.Right); _ilg.MarkLabel(end); } private void EmitMethodOrElse(BinaryExpression b, CompilationFlags flags) { Label labEnd = _ilg.DefineLabel(); EmitExpression(b.Left); _ilg.Emit(OpCodes.Dup); MethodInfo opTrue = TypeUtils.GetBooleanOperator(b.Method.DeclaringType, "op_True"); Debug.Assert(opTrue != null, "factory should check that the method exists"); _ilg.Emit(OpCodes.Call, opTrue); _ilg.Emit(OpCodes.Brtrue, labEnd); //store the value of the left value before emitting b.Right to empty the evaluation stack LocalBuilder locLeft = GetLocal(b.Left.Type); _ilg.Emit(OpCodes.Stloc, locLeft); EmitExpression(b.Right); //store the right value to local LocalBuilder locRight = GetLocal(b.Right.Type); _ilg.Emit(OpCodes.Stloc, locRight); Debug.Assert(b.Method.IsStatic); _ilg.Emit(OpCodes.Ldloc, locLeft); _ilg.Emit(OpCodes.Ldloc, locRight); if ((flags & CompilationFlags.EmitAsTailCallMask) == CompilationFlags.EmitAsTail) { _ilg.Emit(OpCodes.Tailcall); } _ilg.Emit(OpCodes.Call, b.Method); FreeLocal(locLeft); FreeLocal(locRight); _ilg.MarkLabel(labEnd); } private void EmitOrElseBinaryExpression(Expression expr, CompilationFlags flags) { BinaryExpression b = (BinaryExpression)expr; if (b.Method != null && !b.IsLiftedLogical) { EmitMethodOrElse(b, flags); } else if (b.Left.Type == typeof(bool?)) { EmitLiftedOrElse(b); } else if (b.IsLiftedLogical) { EmitExpression(b.ReduceUserdefinedLifted()); } else { EmitUnliftedOrElse(b); } } #endregion #region Optimized branching /// /// Emits the expression and then either brtrue/brfalse to the label. /// /// True for brtrue, false for brfalse. /// The expression to emit. /// The label to conditionally branch to. /// /// This function optimizes equality and short circuiting logical /// operators to avoid double-branching, minimize instruction count, /// and generate similar IL to the C# compiler. This is important for /// the JIT to optimize patterns like: /// x != null AndAlso x.GetType() == typeof(SomeType) /// /// One optimization we don't do: we always emits at least one /// conditional branch to the label, and always possibly falls through, /// even if we know if the branch will always succeed or always fail. /// We do this to avoid generating unreachable code, which is fine for /// the CLR JIT, but doesn't verify with peverify. /// /// This kind of optimization could be implemented safely, by doing /// constant folding over conditionals and logical expressions at the /// tree level. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")] private void EmitExpressionAndBranch(bool branchValue, Expression node, Label label) { CompilationFlags startEmitted = EmitExpressionStart(node); try { if (node.Type == typeof(bool)) { switch (node.NodeType) { case ExpressionType.Not: EmitBranchNot(branchValue, (UnaryExpression)node, label); return; case ExpressionType.AndAlso: case ExpressionType.OrElse: EmitBranchLogical(branchValue, (BinaryExpression)node, label); return; case ExpressionType.Block: EmitBranchBlock(branchValue, (BlockExpression)node, label); return; case ExpressionType.Equal: case ExpressionType.NotEqual: EmitBranchComparison(branchValue, (BinaryExpression)node, label); return; } } EmitExpression(node, CompilationFlags.EmitAsNoTail | CompilationFlags.EmitNoExpressionStart); EmitBranchOp(branchValue, label); } finally { EmitExpressionEnd(startEmitted); } } private void EmitBranchOp(bool branch, Label label) { _ilg.Emit(branch ? OpCodes.Brtrue : OpCodes.Brfalse, label); } private void EmitBranchNot(bool branch, UnaryExpression node, Label label) { if (node.Method != null) { EmitExpression(node, CompilationFlags.EmitAsNoTail | CompilationFlags.EmitNoExpressionStart); EmitBranchOp(branch, label); return; } EmitExpressionAndBranch(!branch, node.Operand, label); } private void EmitBranchComparison(bool branch, BinaryExpression node, Label label) { Debug.Assert(node.NodeType == ExpressionType.Equal || node.NodeType == ExpressionType.NotEqual); Debug.Assert(!node.IsLiftedToNull); // To share code paths, we want to treat NotEqual as an inverted Equal bool branchWhenEqual = branch == (node.NodeType == ExpressionType.Equal); if (node.Method != null) { EmitBinaryMethod(node, CompilationFlags.EmitAsNoTail); // EmitBinaryMethod takes into account the Equal/NotEqual // node kind, so use the original branch value EmitBranchOp(branch, label); } else if (ConstantCheck.IsNull(node.Left)) { if (TypeUtils.IsNullableType(node.Right.Type)) { EmitAddress(node.Right, node.Right.Type); _ilg.EmitHasValue(node.Right.Type); } else { Debug.Assert(!node.Right.Type.IsValueType); EmitExpression(GetEqualityOperand(node.Right)); } EmitBranchOp(!branchWhenEqual, label); } else if (ConstantCheck.IsNull(node.Right)) { if (TypeUtils.IsNullableType(node.Left.Type)) { EmitAddress(node.Left, node.Left.Type); _ilg.EmitHasValue(node.Left.Type); } else { Debug.Assert(!node.Left.Type.IsValueType); EmitExpression(GetEqualityOperand(node.Left)); } EmitBranchOp(!branchWhenEqual, label); } else if (TypeUtils.IsNullableType(node.Left.Type) || TypeUtils.IsNullableType(node.Right.Type)) { EmitBinaryExpression(node); // EmitBinaryExpression takes into account the Equal/NotEqual // node kind, so use the original branch value EmitBranchOp(branch, label); } else { EmitExpression(GetEqualityOperand(node.Left)); EmitExpression(GetEqualityOperand(node.Right)); if (branchWhenEqual) { _ilg.Emit(OpCodes.Beq, label); } else { _ilg.Emit(OpCodes.Ceq); _ilg.Emit(OpCodes.Brfalse, label); } } } // For optimized Equal/NotEqual, we can eliminate reference // conversions. IL allows comparing managed pointers regardless of // type. See ECMA-335 "Binary Comparison or Branch Operations", in // Partition III, Section 1.5 Table 4. private static Expression GetEqualityOperand(Expression expression) { if (expression.NodeType == ExpressionType.Convert) { var convert = (UnaryExpression)expression; if (TypeUtils.AreReferenceAssignable(convert.Type, convert.Operand.Type)) { return convert.Operand; } } return expression; } private void EmitBranchLogical(bool branch, BinaryExpression node, Label label) { Debug.Assert(node.NodeType == ExpressionType.AndAlso || node.NodeType == ExpressionType.OrElse); Debug.Assert(!node.IsLiftedToNull); if (node.Method != null || node.IsLifted) { EmitExpression(node); EmitBranchOp(branch, label); return; } bool isAnd = node.NodeType == ExpressionType.AndAlso; // To share code, we make the following substitutions: // if (!(left || right)) branch value // becomes: // if (!left && !right) branch value // and: // if (!(left && right)) branch value // becomes: // if (!left || !right) branch value // // The observation is that "brtrue(x && y)" has the same codegen as // "brfalse(x || y)" except the branches have the opposite sign. // Same for "brfalse(x && y)" and "brtrue(x || y)". // if (branch == isAnd) { EmitBranchAnd(branch, node, label); } else { EmitBranchOr(branch, node, label); } } // Generates optimized AndAlso with branch == true // or optimized OrElse with branch == false private void EmitBranchAnd(bool branch, BinaryExpression node, Label label) { // if (left) then // if (right) branch label // endif Label endif = _ilg.DefineLabel(); EmitExpressionAndBranch(!branch, node.Left, endif); EmitExpressionAndBranch(branch, node.Right, label); _ilg.MarkLabel(endif); } // Generates optimized OrElse with branch == true // or optimized AndAlso with branch == false private void EmitBranchOr(bool branch, BinaryExpression node, Label label) { // if (left OR right) branch label EmitExpressionAndBranch(branch, node.Left, label); EmitExpressionAndBranch(branch, node.Right, label); } private void EmitBranchBlock(bool branch, BlockExpression node, Label label) { EnterScope(node); int count = node.ExpressionCount; for (int i = 0; i < count - 1; i++) { EmitExpressionAsVoid(node.GetExpression(i)); } EmitExpressionAndBranch(branch, node.GetExpression(count - 1), label); ExitScope(node); } #endregion } }