/* **************************************************************************** * * 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.Collections.Generic; using System.Diagnostics; using System.Dynamic.Utils; using System.Reflection; using System.Reflection.Emit; using System.Globalization; #if !FEATURE_CORE_DLR namespace Microsoft.Scripting.Ast.Compiler { #else namespace System.Linq.Expressions.Compiler { #endif partial class LambdaCompiler { private void EmitBlockExpression(Expression expr, CompilationFlags flags) { // emit body Emit((BlockExpression)expr, UpdateEmitAsTypeFlag(flags, CompilationFlags.EmitAsDefaultType)); } private void Emit(BlockExpression node, CompilationFlags flags) { EnterScope(node); CompilationFlags emitAs = flags & CompilationFlags.EmitAsTypeMask; int count = node.ExpressionCount; CompilationFlags tailCall = flags & CompilationFlags.EmitAsTailCallMask; for (int index = 0; index < count - 1; index++) { var e = node.GetExpression(index); var next = node.GetExpression(index + 1); if (EmitDebugSymbols) { // No need to emit a clearance if the next expression in the block is also a // DebugInfoExprssion. var debugInfo = e as DebugInfoExpression; if (debugInfo != null && debugInfo.IsClear && next is DebugInfoExpression) { continue; } } CompilationFlags tailCallFlag; if (tailCall != CompilationFlags.EmitAsNoTail) { var g = next as GotoExpression; if (g != null && (g.Value == null || !Significant(g.Value)) && ReferenceLabel(g.Target).CanReturn) { // Since tail call flags are not passed into EmitTryExpression, CanReturn means the goto will be emitted // as Ret. Therefore we can emit the current expression with tail call. tailCallFlag = CompilationFlags.EmitAsTail; } else { // In the middle of the block. // We may do better here by marking it as Tail if the following expressions are not going to emit any IL. tailCallFlag = CompilationFlags.EmitAsMiddle; } } else { tailCallFlag = CompilationFlags.EmitAsNoTail; } flags = UpdateEmitAsTailCallFlag(flags, tailCallFlag); EmitExpressionAsVoid(e, flags); } // if the type of Block it means this is not a Comma // so we will force the last expression to emit as void. // We don't need EmitAsType flag anymore, should only pass // the EmitTailCall field in flags to emitting the last expression. if (emitAs == CompilationFlags.EmitAsVoidType || node.Type == typeof(void)) { EmitExpressionAsVoid(node.GetExpression(count - 1), tailCall); } else { EmitExpressionAsType(node.GetExpression(count - 1), node.Type, tailCall); } ExitScope(node); } private void EnterScope(object node) { if (HasVariables(node) && (_scope.MergedScopes == null || !_scope.MergedScopes.Contains(node))) { CompilerScope scope; if (!_tree.Scopes.TryGetValue(node, out scope)) { // // Very often, we want to compile nodes as reductions // rather than as IL, but usually they need to allocate // some IL locals. To support this, we allow emitting a // BlockExpression that was not bound by VariableBinder. // This works as long as the variables are only used // locally -- i.e. not closed over. // // User-created blocks will never hit this case; only our // internally reduced nodes will. // scope = new CompilerScope(node, false) { NeedsClosure = _scope.NeedsClosure }; } _scope = scope.Enter(this, _scope); Debug.Assert(_scope.Node == node); } } private static bool HasVariables(object node) { var block = node as BlockExpression; if (block != null) { return block.Variables.Count > 0; } return ((CatchBlock)node).Variable != null; } private void ExitScope(object node) { if (_scope.Node == node) { _scope = _scope.Exit(); } } private void EmitDefaultExpression(Expression expr) { var node = (DefaultExpression)expr; if (node.Type != typeof(void)) { // emit default(T) _ilg.EmitDefault(node.Type); } } private void EmitLoopExpression(Expression expr) { LoopExpression node = (LoopExpression)expr; PushLabelBlock(LabelScopeKind.Statement); LabelInfo breakTarget = DefineLabel(node.BreakLabel); LabelInfo continueTarget = DefineLabel(node.ContinueLabel); continueTarget.MarkWithEmptyStack(); EmitExpressionAsVoid(node.Body); _ilg.Emit(OpCodes.Br, continueTarget.Label); PopLabelBlock(LabelScopeKind.Statement); breakTarget.MarkWithEmptyStack(); } #region SwitchExpression private void EmitSwitchExpression(Expression expr, CompilationFlags flags) { SwitchExpression node = (SwitchExpression)expr; // Try to emit it as an IL switch. Works for integer types. if (TryEmitSwitchInstruction(node, flags)) { return; } // Try to emit as a hashtable lookup. Works for strings. if (TryEmitHashtableSwitch(node, flags)) { return; } // // Fall back to a series of tests. We need to IL gen instead of // transform the tree to avoid stack overflow on a big switch. // var switchValue = Expression.Parameter(node.SwitchValue.Type, "switchValue"); var testValue = Expression.Parameter(GetTestValueType(node), "testValue"); _scope.AddLocal(this, switchValue); _scope.AddLocal(this, testValue); EmitExpression(node.SwitchValue); _scope.EmitSet(switchValue); // Emit tests var labels = new Label[node.Cases.Count]; var isGoto = new bool[node.Cases.Count]; for (int i = 0, n = node.Cases.Count; i < n; i++) { DefineSwitchCaseLabel(node.Cases[i], out labels[i], out isGoto[i]); foreach (Expression test in node.Cases[i].TestValues) { // Pull the test out into a temp so it runs on the same // stack as the switch. This simplifies spilling. EmitExpression(test); _scope.EmitSet(testValue); Debug.Assert(TypeUtils.AreReferenceAssignable(testValue.Type, test.Type)); EmitExpressionAndBranch(true, Expression.Equal(switchValue, testValue, false, node.Comparison), labels[i]); } } // Define labels Label end = _ilg.DefineLabel(); Label @default = (node.DefaultBody == null) ? end : _ilg.DefineLabel(); // Emit the case and default bodies EmitSwitchCases(node, labels, isGoto, @default, end, flags); } /// /// Gets the common test test value type of the SwitchExpression. /// private static Type GetTestValueType(SwitchExpression node) { if (node.Comparison == null) { // If we have no comparison, all right side types must be the // same. return node.Cases[0].TestValues[0].Type; } // Otherwise, get the type from the method. Type result = node.Comparison.GetParametersCached()[1].ParameterType.GetNonRefType(); if (node.IsLifted) { result = TypeUtils.GetNullableType(result); } return result; } private sealed class SwitchLabel { internal readonly decimal Key; internal readonly Label Label; // Boxed version of Key, preseving the original type. internal readonly object Constant; internal SwitchLabel(decimal key, object @constant, Label label) { Key = key; Constant = @constant; Label = label; } } private sealed class SwitchInfo { internal readonly SwitchExpression Node; internal readonly LocalBuilder Value; internal readonly Label Default; internal readonly Type Type; internal readonly bool IsUnsigned; internal readonly bool Is64BitSwitch; internal SwitchInfo(SwitchExpression node, LocalBuilder value, Label @default) { Node = node; Value = value; Default = @default; Type = Node.SwitchValue.Type; IsUnsigned = TypeUtils.IsUnsigned(Type); var code = Type.GetTypeCode(Type); Is64BitSwitch = code == TypeCode.UInt64 || code == TypeCode.Int64; } } private static bool FitsInBucket(List buckets, decimal key, int count) { Debug.Assert(key > buckets[buckets.Count - 1].Key); decimal jumpTableSlots = key - buckets[0].Key + 1; if (jumpTableSlots > int.MaxValue) { return false; } // density must be > 50% return (buckets.Count + count) * 2 > jumpTableSlots; } private static void MergeBuckets(List> buckets) { while (buckets.Count > 1) { List first = buckets[buckets.Count - 2]; List second = buckets[buckets.Count - 1]; if (!FitsInBucket(first, second[second.Count - 1].Key, second.Count)) { return; } // Merge them first.AddRange(second); buckets.RemoveAt(buckets.Count - 1); } } // Add key to a new or existing bucket private static void AddToBuckets(List> buckets, SwitchLabel key) { if (buckets.Count > 0) { List last = buckets[buckets.Count - 1]; if (FitsInBucket(last, key.Key, 1)) { last.Add(key); // we might be able to merge now MergeBuckets(buckets); return; } } // else create a new bucket buckets.Add(new List { key }); } // Determines if the type is an integer we can switch on. private static bool CanOptimizeSwitchType(Type valueType) { // enums & char are allowed switch (Type.GetTypeCode(valueType)) { case TypeCode.Byte: case TypeCode.SByte: case TypeCode.Char: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.Int64: case TypeCode.UInt64: return true; default: return false; } } // Tries to emit switch as a jmp table private bool TryEmitSwitchInstruction(SwitchExpression node, CompilationFlags flags) { // If we have a comparison, bail if (node.Comparison != null) { return false; } // Make sure the switch value type and the right side type // are types we can optimize Type type = node.SwitchValue.Type; if (!CanOptimizeSwitchType(type) || !TypeUtils.AreEquivalent(type, node.Cases[0].TestValues[0].Type)) { return false; } // Make sure all test values are constant, or we can't emit the // jump table. if (!node.Cases.All(c => c.TestValues.All(t => t is ConstantExpression))) { return false; } // // We can emit the optimized switch, let's do it. // // Build target labels, collect keys. var labels = new Label[node.Cases.Count]; var isGoto = new bool[node.Cases.Count]; var uniqueKeys = new Set(); var keys = new List(); for (int i = 0; i < node.Cases.Count; i++) { DefineSwitchCaseLabel(node.Cases[i], out labels[i], out isGoto[i]); foreach (ConstantExpression test in node.Cases[i].TestValues) { // Guarenteed to work thanks to CanOptimizeSwitchType. // // Use decimal because it can hold Int64 or UInt64 without // precision loss or signed/unsigned conversions. decimal key = ConvertSwitchValue(test.Value); // Only add each key once. If it appears twice, it's // allowed, but can't be reached. if (!uniqueKeys.Contains(key)) { keys.Add(new SwitchLabel(key, test.Value, labels[i])); uniqueKeys.Add(key); } } } // Sort the keys, and group them into buckets. keys.Sort((x, y) => Math.Sign(x.Key - y.Key)); var buckets = new List>(); foreach (var key in keys) { AddToBuckets(buckets, key); } // Emit the switchValue LocalBuilder value = GetLocal(node.SwitchValue.Type); EmitExpression(node.SwitchValue); _ilg.Emit(OpCodes.Stloc, value); // Create end label, and default label if needed Label end = _ilg.DefineLabel(); Label @default = (node.DefaultBody == null) ? end : _ilg.DefineLabel(); // Emit the switch var info = new SwitchInfo(node, value, @default); EmitSwitchBuckets(info, buckets, 0, buckets.Count - 1); // Emit the case bodies and default EmitSwitchCases(node, labels, isGoto, @default, end, flags); FreeLocal(value); return true; } private static decimal ConvertSwitchValue(object value) { if (value is char) { return (int)(char)value; } return Convert.ToDecimal(value, CultureInfo.InvariantCulture); } /// /// Creates the label for this case. /// Optimization: if the body is just a goto, and we can branch /// to it, put the goto target directly in the jump table. /// private void DefineSwitchCaseLabel(SwitchCase @case, out Label label, out bool isGoto) { var jump = @case.Body as GotoExpression; // if it's a goto with no value if (jump != null && jump.Value == null) { // Reference the label from the switch. This will cause us to // analyze the jump target and determine if it is safe. LabelInfo jumpInfo = ReferenceLabel(jump.Target); // If we have are allowed to emit the "branch" opcode, then we // can jump directly there from the switch's jump table. // (Otherwise, we need to emit the goto later as a "leave".) if (jumpInfo.CanBranch) { label = jumpInfo.Label; isGoto = true; return; } } // otherwise, just define a new label label = _ilg.DefineLabel(); isGoto = false; } private void EmitSwitchCases(SwitchExpression node, Label[] labels, bool[] isGoto, Label @default, Label end, CompilationFlags flags) { // Jump to default (to handle the fallthrough case) _ilg.Emit(OpCodes.Br, @default); // Emit the cases for (int i = 0, n = node.Cases.Count; i < n; i++) { // If the body is a goto, we already emitted an optimized // branch directly to it. No need to emit anything else. if (isGoto[i]) { continue; } _ilg.MarkLabel(labels[i]); EmitExpressionAsType(node.Cases[i].Body, node.Type, flags); // Last case doesn't need branch if (node.DefaultBody != null || i < n - 1) { if ((flags & CompilationFlags.EmitAsTailCallMask) == CompilationFlags.EmitAsTail) { //The switch case is at the tail of the lambda so //it is safe to emit a Ret. _ilg.Emit(OpCodes.Ret); } else { _ilg.Emit(OpCodes.Br, end); } } } // Default value if (node.DefaultBody != null) { _ilg.MarkLabel(@default); EmitExpressionAsType(node.DefaultBody, node.Type, flags); } _ilg.MarkLabel(end); } private void EmitSwitchBuckets(SwitchInfo info, List> buckets, int first, int last) { if (first == last) { EmitSwitchBucket(info, buckets[first]); return; } // Split the buckets into two groups, and use an if test to find // the right bucket. This ensures we'll only need O(lg(B)) tests // where B is the number of buckets int mid = (int)(((long)first + last + 1) / 2); if (first == mid - 1) { EmitSwitchBucket(info, buckets[first]); } else { // If the first half contains more than one, we need to emit an // explicit guard Label secondHalf = _ilg.DefineLabel(); _ilg.Emit(OpCodes.Ldloc, info.Value); _ilg.EmitConstant(buckets[mid - 1].Last().Constant); _ilg.Emit(info.IsUnsigned ? OpCodes.Bgt_Un : OpCodes.Bgt, secondHalf); EmitSwitchBuckets(info, buckets, first, mid - 1); _ilg.MarkLabel(secondHalf); } EmitSwitchBuckets(info, buckets, mid, last); } private void EmitSwitchBucket(SwitchInfo info, List bucket) { // No need for switch if we only have one value if (bucket.Count == 1) { _ilg.Emit(OpCodes.Ldloc, info.Value); _ilg.EmitConstant(bucket[0].Constant); _ilg.Emit(OpCodes.Beq, bucket[0].Label); return; } // // If we're switching off of Int64/UInt64, we need more guards here // because we'll have to narrow the switch value to an Int32, and // we can't do that unless the value is in the right range. // Label? after = null; if (info.Is64BitSwitch) { after = _ilg.DefineLabel(); _ilg.Emit(OpCodes.Ldloc, info.Value); _ilg.EmitConstant(bucket.Last().Constant); _ilg.Emit(info.IsUnsigned ? OpCodes.Bgt_Un : OpCodes.Bgt, after.Value); _ilg.Emit(OpCodes.Ldloc, info.Value); _ilg.EmitConstant(bucket[0].Constant); _ilg.Emit(info.IsUnsigned ? OpCodes.Blt_Un : OpCodes.Blt, after.Value); } _ilg.Emit(OpCodes.Ldloc, info.Value); // Normalize key decimal key = bucket[0].Key; if (key != 0) { _ilg.EmitConstant(bucket[0].Constant); _ilg.Emit(OpCodes.Sub); } if (info.Is64BitSwitch) { _ilg.Emit(OpCodes.Conv_I4); } // Collect labels int len = (int)(bucket[bucket.Count - 1].Key - bucket[0].Key + 1); Label[] jmpLabels = new Label[len]; // Initialize all labels to the default int slot = 0; foreach (SwitchLabel label in bucket) { while (key++ != label.Key) { jmpLabels[slot++] = info.Default; } jmpLabels[slot++] = label.Label; } // check we used all keys and filled all slots Debug.Assert(key == bucket[bucket.Count - 1].Key + 1); Debug.Assert(slot == jmpLabels.Length); // Finally, emit the switch instruction _ilg.Emit(OpCodes.Switch, jmpLabels); if (info.Is64BitSwitch) { _ilg.MarkLabel(after.Value); } } private bool TryEmitHashtableSwitch(SwitchExpression node, CompilationFlags flags) { // If we have a comparison other than string equality, bail if (node.Comparison != typeof(string).GetMethod("op_Equality", BindingFlags.Public | BindingFlags.Static | BindingFlags.ExactBinding, null, new[] { typeof(string), typeof(string) }, null)) { return false; } // All test values must be constant. int tests = 0; foreach (SwitchCase c in node.Cases) { foreach (Expression t in c.TestValues) { if (!(t is ConstantExpression)) { return false; } tests++; } } // Must have >= 7 labels for it to be worth it. if (tests < 7) { return false; } // If we're in a DynamicMethod, we could just build the dictionary // immediately. But that would cause the two code paths to be more // different than they really need to be. var initializers = new List(tests); var cases = new List(node.Cases.Count); int nullCase = -1; MethodInfo add = typeof(Dictionary).GetMethod("Add", new[] { typeof(string), typeof(int) }); for (int i = 0, n = node.Cases.Count; i < n; i++) { foreach (ConstantExpression t in node.Cases[i].TestValues) { if (t.Value != null) { initializers.Add(Expression.ElementInit(add, t, Expression.Constant(i))); } else { nullCase = i; } } cases.Add(Expression.SwitchCase(node.Cases[i].Body, Expression.Constant(i))); } // Create the field to hold the lazily initialized dictionary MemberExpression dictField = CreateLazyInitializedField>("dictionarySwitch"); // If we happen to initialize it twice (multithreaded case), it's // not the end of the world. The C# compiler does better here by // emitting a volatile access to the field. Expression dictInit = Expression.Condition( Expression.Equal(dictField, Expression.Constant(null, dictField.Type)), Expression.Assign( dictField, Expression.ListInit( Expression.New( typeof(Dictionary).GetConstructor(new[] { typeof(int) }), Expression.Constant(initializers.Count) ), initializers ) ), dictField ); // // Create a tree like: // // switchValue = switchValueExpression; // if (switchValue == null) { // switchIndex = nullCase; // } else { // if (_dictField == null) { // _dictField = new Dictionary(count) { { ... }, ... }; // } // if (!_dictField.TryGetValue(switchValue, out switchIndex)) { // switchIndex = -1; // } // } // switch (switchIndex) { // case 0: ... // case 1: ... // ... // default: // } // var switchValue = Expression.Variable(typeof(string), "switchValue"); var switchIndex = Expression.Variable(typeof(int), "switchIndex"); var reduced = Expression.Block( new[] { switchIndex, switchValue }, Expression.Assign(switchValue, node.SwitchValue), Expression.IfThenElse( Expression.Equal(switchValue, Expression.Constant(null, typeof(string))), Expression.Assign(switchIndex, Expression.Constant(nullCase)), Expression.IfThenElse( Expression.Call(dictInit, "TryGetValue", null, switchValue, switchIndex), Expression.Empty(), Expression.Assign(switchIndex, Expression.Constant(-1)) ) ), Expression.Switch(node.Type, switchIndex, node.DefaultBody, null, cases) ); EmitExpression(reduced, flags); return true; } #endregion 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(); } #region TryStatement private void CheckTry() { // Try inside a filter is not verifiable for (LabelScopeInfo j = _labelBlock; j != null; j = j.Parent) { if (j.Kind == LabelScopeKind.Filter) { throw Error.TryNotAllowedInFilter(); } } } private void EmitSaveExceptionOrPop(CatchBlock cb) { if (cb.Variable != null) { // If the variable is present, store the exception // in the variable. _scope.EmitSet(cb.Variable); } else { // Otherwise, pop it off the stack. _ilg.Emit(OpCodes.Pop); } } private void EmitTryExpression(Expression expr) { var node = (TryExpression)expr; CheckTry(); //****************************************************************** // 1. ENTERING TRY //****************************************************************** PushLabelBlock(LabelScopeKind.Try); _ilg.BeginExceptionBlock(); //****************************************************************** // 2. Emit the try statement body //****************************************************************** EmitExpression(node.Body); Type tryType = expr.Type; LocalBuilder value = null; if (tryType != typeof(void)) { //store the value of the try body value = GetLocal(tryType); _ilg.Emit(OpCodes.Stloc, value); } //****************************************************************** // 3. Emit the catch blocks //****************************************************************** foreach (CatchBlock cb in node.Handlers) { PushLabelBlock(LabelScopeKind.Catch); // Begin the strongly typed exception block if (cb.Filter == null) { _ilg.BeginCatchBlock(cb.Test); } else { _ilg.BeginExceptFilterBlock(); } EnterScope(cb); EmitCatchStart(cb); // // Emit the catch block body // EmitExpression(cb.Body); if (tryType != typeof(void)) { //store the value of the catch block body _ilg.Emit(OpCodes.Stloc, value); } ExitScope(cb); PopLabelBlock(LabelScopeKind.Catch); } //****************************************************************** // 4. Emit the finally block //****************************************************************** if (node.Finally != null || node.Fault != null) { PushLabelBlock(LabelScopeKind.Finally); if (node.Finally != null) { _ilg.BeginFinallyBlock(); } else { _ilg.BeginFaultBlock(); } // Emit the body EmitExpressionAsVoid(node.Finally ?? node.Fault); _ilg.EndExceptionBlock(); PopLabelBlock(LabelScopeKind.Finally); } else { _ilg.EndExceptionBlock(); } if (tryType != typeof(void)) { _ilg.Emit(OpCodes.Ldloc, value); FreeLocal(value); } PopLabelBlock(LabelScopeKind.Try); } /// /// Emits the start of a catch block. The exception value that is provided by the /// CLR is stored in the variable specified by the catch block or popped if no /// variable is provided. /// private void EmitCatchStart(CatchBlock cb) { if (cb.Filter == null) { EmitSaveExceptionOrPop(cb); return; } // emit filter block. Filter blocks are untyped so we need to do // the type check ourselves. Label endFilter = _ilg.DefineLabel(); Label rightType = _ilg.DefineLabel(); // skip if it's not our exception type, but save // the exception if it is so it's available to the // filter _ilg.Emit(OpCodes.Isinst, cb.Test); _ilg.Emit(OpCodes.Dup); _ilg.Emit(OpCodes.Brtrue, rightType); _ilg.Emit(OpCodes.Pop); _ilg.Emit(OpCodes.Ldc_I4_0); _ilg.Emit(OpCodes.Br, endFilter); // it's our type, save it and emit the filter. _ilg.MarkLabel(rightType); EmitSaveExceptionOrPop(cb); PushLabelBlock(LabelScopeKind.Filter); EmitExpression(cb.Filter); PopLabelBlock(LabelScopeKind.Filter); // begin the catch, clear the exception, we've // already saved it _ilg.MarkLabel(endFilter); _ilg.BeginCatchBlock(null); _ilg.Emit(OpCodes.Pop); } #endregion } }