1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Collections.Generic; 6 using System.Diagnostics; 7 using System.Dynamic.Utils; 8 using System.Globalization; 9 using System.Reflection; 10 using System.Reflection.Emit; 11 using System.Runtime.CompilerServices; 12 using static System.Linq.Expressions.CachedReflectionInfo; 13 14 namespace System.Linq.Expressions.Compiler 15 { 16 internal partial class LambdaCompiler 17 { EmitBlockExpression(Expression expr, CompilationFlags flags)18 private void EmitBlockExpression(Expression expr, CompilationFlags flags) 19 { 20 // emit body 21 Emit((BlockExpression)expr, UpdateEmitAsTypeFlag(flags, CompilationFlags.EmitAsDefaultType)); 22 } 23 Emit(BlockExpression node, CompilationFlags flags)24 private void Emit(BlockExpression node, CompilationFlags flags) 25 { 26 int count = node.ExpressionCount; 27 28 if (count == 0) 29 { 30 return; 31 } 32 33 EnterScope(node); 34 35 CompilationFlags emitAs = flags & CompilationFlags.EmitAsTypeMask; 36 37 CompilationFlags tailCall = flags & CompilationFlags.EmitAsTailCallMask; 38 for (int index = 0; index < count - 1; index++) 39 { 40 Expression e = node.GetExpression(index); 41 Expression next = node.GetExpression(index + 1); 42 43 CompilationFlags tailCallFlag; 44 if (tailCall != CompilationFlags.EmitAsNoTail) 45 { 46 var g = next as GotoExpression; 47 if (g != null && (g.Value == null || !Significant(g.Value)) && ReferenceLabel(g.Target).CanReturn) 48 { 49 // Since tail call flags are not passed into EmitTryExpression, CanReturn means the goto will be emitted 50 // as Ret. Therefore we can emit the current expression with tail call. 51 tailCallFlag = CompilationFlags.EmitAsTail; 52 } 53 else 54 { 55 // In the middle of the block. 56 // We may do better here by marking it as Tail if the following expressions are not going to emit any IL. 57 tailCallFlag = CompilationFlags.EmitAsMiddle; 58 } 59 } 60 else 61 { 62 tailCallFlag = CompilationFlags.EmitAsNoTail; 63 } 64 65 flags = UpdateEmitAsTailCallFlag(flags, tailCallFlag); 66 EmitExpressionAsVoid(e, flags); 67 } 68 69 // if the type of Block it means this is not a Comma 70 // so we will force the last expression to emit as void. 71 // We don't need EmitAsType flag anymore, should only pass 72 // the EmitTailCall field in flags to emitting the last expression. 73 if (emitAs == CompilationFlags.EmitAsVoidType || node.Type == typeof(void)) 74 { 75 EmitExpressionAsVoid(node.GetExpression(count - 1), tailCall); 76 } 77 else 78 { 79 EmitExpressionAsType(node.GetExpression(count - 1), node.Type, tailCall); 80 } 81 82 ExitScope(node); 83 } 84 EnterScope(object node)85 private void EnterScope(object node) 86 { 87 if (HasVariables(node) && 88 (_scope.MergedScopes == null || !_scope.MergedScopes.Contains(node))) 89 { 90 CompilerScope scope; 91 if (!_tree.Scopes.TryGetValue(node, out scope)) 92 { 93 // 94 // Very often, we want to compile nodes as reductions 95 // rather than as IL, but usually they need to allocate 96 // some IL locals. To support this, we allow emitting a 97 // BlockExpression that was not bound by VariableBinder. 98 // This works as long as the variables are only used 99 // locally -- i.e. not closed over. 100 // 101 // User-created blocks will never hit this case; only our 102 // internally reduced nodes will. 103 // 104 scope = new CompilerScope(node, false) { NeedsClosure = _scope.NeedsClosure }; 105 } 106 107 _scope = scope.Enter(this, _scope); 108 Debug.Assert(_scope.Node == node); 109 } 110 } 111 HasVariables(object node)112 private static bool HasVariables(object node) 113 { 114 var block = node as BlockExpression; 115 if (block != null) 116 { 117 return block.Variables.Count > 0; 118 } 119 return ((CatchBlock)node).Variable != null; 120 } 121 ExitScope(object node)122 private void ExitScope(object node) 123 { 124 if (_scope.Node == node) 125 { 126 _scope = _scope.Exit(); 127 } 128 } 129 EmitDefaultExpression(Expression expr)130 private void EmitDefaultExpression(Expression expr) 131 { 132 var node = (DefaultExpression)expr; 133 if (node.Type != typeof(void)) 134 { 135 // emit default(T) 136 _ilg.EmitDefault(node.Type, this); 137 } 138 } 139 EmitLoopExpression(Expression expr)140 private void EmitLoopExpression(Expression expr) 141 { 142 LoopExpression node = (LoopExpression)expr; 143 144 PushLabelBlock(LabelScopeKind.Statement); 145 LabelInfo breakTarget = DefineLabel(node.BreakLabel); 146 LabelInfo continueTarget = DefineLabel(node.ContinueLabel); 147 148 continueTarget.MarkWithEmptyStack(); 149 150 EmitExpressionAsVoid(node.Body); 151 152 _ilg.Emit(OpCodes.Br, continueTarget.Label); 153 154 PopLabelBlock(LabelScopeKind.Statement); 155 156 breakTarget.MarkWithEmptyStack(); 157 } 158 159 #region SwitchExpression 160 EmitSwitchExpression(Expression expr, CompilationFlags flags)161 private void EmitSwitchExpression(Expression expr, CompilationFlags flags) 162 { 163 SwitchExpression node = (SwitchExpression)expr; 164 165 if (node.Cases.Count == 0) 166 { 167 // Emit the switch value in case it has side-effects, but as void 168 // since the value is ignored. 169 EmitExpressionAsVoid(node.SwitchValue); 170 171 // Now if there is a default body, it happens unconditionally. 172 if (node.DefaultBody != null) 173 { 174 EmitExpressionAsType(node.DefaultBody, node.Type, flags); 175 } 176 else 177 { 178 // If there are no cases and no default then the type must be void. 179 // Assert that earlier validation caught any exceptions to that. 180 Debug.Assert(node.Type == typeof(void)); 181 } 182 183 return; 184 } 185 186 // Try to emit it as an IL switch. Works for integer types. 187 if (TryEmitSwitchInstruction(node, flags)) 188 { 189 return; 190 } 191 192 // Try to emit as a hashtable lookup. Works for strings. 193 if (TryEmitHashtableSwitch(node, flags)) 194 { 195 return; 196 } 197 198 // 199 // Fall back to a series of tests. We need to IL gen instead of 200 // transform the tree to avoid stack overflow on a big switch. 201 // 202 203 ParameterExpression switchValue = Expression.Parameter(node.SwitchValue.Type, "switchValue"); 204 ParameterExpression testValue = Expression.Parameter(GetTestValueType(node), "testValue"); 205 _scope.AddLocal(this, switchValue); 206 _scope.AddLocal(this, testValue); 207 208 EmitExpression(node.SwitchValue); 209 _scope.EmitSet(switchValue); 210 211 // Emit tests 212 var labels = new Label[node.Cases.Count]; 213 var isGoto = new bool[node.Cases.Count]; 214 for (int i = 0, n = node.Cases.Count; i < n; i++) 215 { 216 DefineSwitchCaseLabel(node.Cases[i], out labels[i], out isGoto[i]); 217 foreach (Expression test in node.Cases[i].TestValues) 218 { 219 // Pull the test out into a temp so it runs on the same 220 // stack as the switch. This simplifies spilling. 221 EmitExpression(test); 222 _scope.EmitSet(testValue); 223 Debug.Assert(TypeUtils.AreReferenceAssignable(testValue.Type, test.Type)); 224 EmitExpressionAndBranch(true, Expression.Equal(switchValue, testValue, false, node.Comparison), labels[i]); 225 } 226 } 227 228 // Define labels 229 Label end = _ilg.DefineLabel(); 230 Label @default = (node.DefaultBody == null) ? end : _ilg.DefineLabel(); 231 232 // Emit the case and default bodies 233 EmitSwitchCases(node, labels, isGoto, @default, end, flags); 234 } 235 236 /// <summary> 237 /// Gets the common test value type of the SwitchExpression. 238 /// </summary> GetTestValueType(SwitchExpression node)239 private static Type GetTestValueType(SwitchExpression node) 240 { 241 if (node.Comparison == null) 242 { 243 // If we have no comparison, all right side types must be the 244 // same. 245 return node.Cases[0].TestValues[0].Type; 246 } 247 248 // Otherwise, get the type from the method. 249 Type result = node.Comparison.GetParametersCached()[1].ParameterType.GetNonRefType(); 250 if (node.IsLifted) 251 { 252 result = result.GetNullableType(); 253 } 254 return result; 255 } 256 257 private sealed class SwitchLabel 258 { 259 internal readonly decimal Key; 260 internal readonly Label Label; 261 262 // Boxed version of Key, preserving the original type. 263 internal readonly object Constant; 264 SwitchLabel(decimal key, object @constant, Label label)265 internal SwitchLabel(decimal key, object @constant, Label label) 266 { 267 Key = key; 268 Constant = @constant; 269 Label = label; 270 } 271 } 272 273 private sealed class SwitchInfo 274 { 275 internal readonly SwitchExpression Node; 276 internal readonly LocalBuilder Value; 277 internal readonly Label Default; 278 internal readonly Type Type; 279 internal readonly bool IsUnsigned; 280 internal readonly bool Is64BitSwitch; 281 SwitchInfo(SwitchExpression node, LocalBuilder value, Label @default)282 internal SwitchInfo(SwitchExpression node, LocalBuilder value, Label @default) 283 { 284 Node = node; 285 Value = value; 286 Default = @default; 287 Type = Node.SwitchValue.Type; 288 IsUnsigned = Type.IsUnsigned(); 289 TypeCode code = Type.GetTypeCode(); 290 Is64BitSwitch = code == TypeCode.UInt64 || code == TypeCode.Int64; 291 } 292 } 293 FitsInBucket(List<SwitchLabel> buckets, decimal key, int count)294 private static bool FitsInBucket(List<SwitchLabel> buckets, decimal key, int count) 295 { 296 Debug.Assert(key > buckets[buckets.Count - 1].Key); 297 decimal jumpTableSlots = key - buckets[0].Key + 1; 298 if (jumpTableSlots > int.MaxValue) 299 { 300 return false; 301 } 302 // density must be > 50% 303 return (buckets.Count + count) * 2 > jumpTableSlots; 304 } 305 MergeBuckets(List<List<SwitchLabel>> buckets)306 private static void MergeBuckets(List<List<SwitchLabel>> buckets) 307 { 308 while (buckets.Count > 1) 309 { 310 List<SwitchLabel> first = buckets[buckets.Count - 2]; 311 List<SwitchLabel> second = buckets[buckets.Count - 1]; 312 313 if (!FitsInBucket(first, second[second.Count - 1].Key, second.Count)) 314 { 315 return; 316 } 317 318 // Merge them 319 first.AddRange(second); 320 buckets.RemoveAt(buckets.Count - 1); 321 } 322 } 323 324 // Add key to a new or existing bucket AddToBuckets(List<List<SwitchLabel>> buckets, SwitchLabel key)325 private static void AddToBuckets(List<List<SwitchLabel>> buckets, SwitchLabel key) 326 { 327 if (buckets.Count > 0) 328 { 329 List<SwitchLabel> last = buckets[buckets.Count - 1]; 330 if (FitsInBucket(last, key.Key, 1)) 331 { 332 last.Add(key); 333 // we might be able to merge now 334 MergeBuckets(buckets); 335 return; 336 } 337 } 338 // else create a new bucket 339 buckets.Add(new List<SwitchLabel> { key }); 340 } 341 342 // Determines if the type is an integer we can switch on. CanOptimizeSwitchType(Type valueType)343 private static bool CanOptimizeSwitchType(Type valueType) 344 { 345 // enums & char are allowed 346 switch (valueType.GetTypeCode()) 347 { 348 case TypeCode.Byte: 349 case TypeCode.SByte: 350 case TypeCode.Char: 351 case TypeCode.Int16: 352 case TypeCode.Int32: 353 case TypeCode.UInt16: 354 case TypeCode.UInt32: 355 case TypeCode.Int64: 356 case TypeCode.UInt64: 357 return true; 358 default: 359 return false; 360 } 361 } 362 363 // Tries to emit switch as a jmp table TryEmitSwitchInstruction(SwitchExpression node, CompilationFlags flags)364 private bool TryEmitSwitchInstruction(SwitchExpression node, CompilationFlags flags) 365 { 366 // If we have a comparison, bail 367 if (node.Comparison != null) 368 { 369 return false; 370 } 371 372 // Make sure the switch value type and the right side type 373 // are types we can optimize 374 Type type = node.SwitchValue.Type; 375 if (!CanOptimizeSwitchType(type) || 376 !TypeUtils.AreEquivalent(type, node.Cases[0].TestValues[0].Type)) 377 { 378 return false; 379 } 380 381 // Make sure all test values are constant, or we can't emit the 382 // jump table. 383 if (!node.Cases.All(c => c.TestValues.All(t => t is ConstantExpression))) 384 { 385 return false; 386 } 387 388 // 389 // We can emit the optimized switch, let's do it. 390 // 391 392 // Build target labels, collect keys. 393 var labels = new Label[node.Cases.Count]; 394 var isGoto = new bool[node.Cases.Count]; 395 396 var uniqueKeys = new HashSet<decimal>(); 397 var keys = new List<SwitchLabel>(); 398 for (int i = 0; i < node.Cases.Count; i++) 399 { 400 DefineSwitchCaseLabel(node.Cases[i], out labels[i], out isGoto[i]); 401 402 foreach (ConstantExpression test in node.Cases[i].TestValues) 403 { 404 // Guaranteed to work thanks to CanOptimizeSwitchType. 405 // 406 // Use decimal because it can hold Int64 or UInt64 without 407 // precision loss or signed/unsigned conversions. 408 decimal key = ConvertSwitchValue(test.Value); 409 410 // Only add each key once. If it appears twice, it's 411 // allowed, but can't be reached. 412 if (uniqueKeys.Add(key)) 413 { 414 keys.Add(new SwitchLabel(key, test.Value, labels[i])); 415 } 416 } 417 } 418 419 // Sort the keys, and group them into buckets. 420 keys.Sort((x, y) => Math.Sign(x.Key - y.Key)); 421 var buckets = new List<List<SwitchLabel>>(); 422 foreach (SwitchLabel key in keys) 423 { 424 AddToBuckets(buckets, key); 425 } 426 427 // Emit the switchValue 428 LocalBuilder value = GetLocal(node.SwitchValue.Type); 429 EmitExpression(node.SwitchValue); 430 _ilg.Emit(OpCodes.Stloc, value); 431 432 // Create end label, and default label if needed 433 Label end = _ilg.DefineLabel(); 434 Label @default = (node.DefaultBody == null) ? end : _ilg.DefineLabel(); 435 436 // Emit the switch 437 var info = new SwitchInfo(node, value, @default); 438 EmitSwitchBuckets(info, buckets, 0, buckets.Count - 1); 439 440 // Emit the case bodies and default 441 EmitSwitchCases(node, labels, isGoto, @default, end, flags); 442 443 FreeLocal(value); 444 return true; 445 } 446 ConvertSwitchValue(object value)447 private static decimal ConvertSwitchValue(object value) 448 { 449 if (value is char) 450 { 451 return (int)(char)value; 452 } 453 return Convert.ToDecimal(value, CultureInfo.InvariantCulture); 454 } 455 456 /// <summary> 457 /// Creates the label for this case. 458 /// Optimization: if the body is just a goto, and we can branch 459 /// to it, put the goto target directly in the jump table. 460 /// </summary> 461 private void DefineSwitchCaseLabel(SwitchCase @case, out Label label, out bool isGoto) 462 { 463 var jump = @case.Body as GotoExpression; 464 // if it's a goto with no value 465 if (jump != null && jump.Value == null) 466 { 467 // Reference the label from the switch. This will cause us to 468 // analyze the jump target and determine if it is safe. 469 LabelInfo jumpInfo = ReferenceLabel(jump.Target); 470 471 // If we have are allowed to emit the "branch" opcode, then we 472 // can jump directly there from the switch's jump table. 473 // (Otherwise, we need to emit the goto later as a "leave".) 474 if (jumpInfo.CanBranch) 475 { 476 label = jumpInfo.Label; 477 isGoto = true; 478 return; 479 } 480 } 481 // otherwise, just define a new label 482 label = _ilg.DefineLabel(); 483 isGoto = false; 484 } 485 EmitSwitchCases(SwitchExpression node, Label[] labels, bool[] isGoto, Label @default, Label end, CompilationFlags flags)486 private void EmitSwitchCases(SwitchExpression node, Label[] labels, bool[] isGoto, Label @default, Label end, CompilationFlags flags) 487 { 488 // Jump to default (to handle the fallthrough case) 489 _ilg.Emit(OpCodes.Br, @default); 490 491 // Emit the cases 492 for (int i = 0, n = node.Cases.Count; i < n; i++) 493 { 494 // If the body is a goto, we already emitted an optimized 495 // branch directly to it. No need to emit anything else. 496 if (isGoto[i]) 497 { 498 continue; 499 } 500 501 _ilg.MarkLabel(labels[i]); 502 EmitExpressionAsType(node.Cases[i].Body, node.Type, flags); 503 504 // Last case doesn't need branch 505 if (node.DefaultBody != null || i < n - 1) 506 { 507 if ((flags & CompilationFlags.EmitAsTailCallMask) == CompilationFlags.EmitAsTail) 508 { 509 //The switch case is at the tail of the lambda so 510 //it is safe to emit a Ret. 511 _ilg.Emit(OpCodes.Ret); 512 } 513 else 514 { 515 _ilg.Emit(OpCodes.Br, end); 516 } 517 } 518 } 519 520 // Default value 521 if (node.DefaultBody != null) 522 { 523 _ilg.MarkLabel(@default); 524 EmitExpressionAsType(node.DefaultBody, node.Type, flags); 525 } 526 527 _ilg.MarkLabel(end); 528 } 529 EmitSwitchBuckets(SwitchInfo info, List<List<SwitchLabel>> buckets, int first, int last)530 private void EmitSwitchBuckets(SwitchInfo info, List<List<SwitchLabel>> buckets, int first, int last) 531 { 532 for (;;) 533 { 534 if (first == last) 535 { 536 EmitSwitchBucket(info, buckets[first]); 537 return; 538 } 539 540 // Split the buckets into two groups, and use an if test to find 541 // the right bucket. This ensures we'll only need O(lg(B)) tests 542 // where B is the number of buckets 543 int mid = (int)(((long)first + last + 1) / 2); 544 545 if (first == mid - 1) 546 { 547 EmitSwitchBucket(info, buckets[first]); 548 } 549 else 550 { 551 // If the first half contains more than one, we need to emit an 552 // explicit guard 553 Label secondHalf = _ilg.DefineLabel(); 554 _ilg.Emit(OpCodes.Ldloc, info.Value); 555 EmitConstant(buckets[mid - 1].Last().Constant); 556 _ilg.Emit(info.IsUnsigned ? OpCodes.Bgt_Un : OpCodes.Bgt, secondHalf); 557 EmitSwitchBuckets(info, buckets, first, mid - 1); 558 _ilg.MarkLabel(secondHalf); 559 } 560 561 first = mid; 562 } 563 } 564 EmitSwitchBucket(SwitchInfo info, List<SwitchLabel> bucket)565 private void EmitSwitchBucket(SwitchInfo info, List<SwitchLabel> bucket) 566 { 567 // No need for switch if we only have one value 568 if (bucket.Count == 1) 569 { 570 _ilg.Emit(OpCodes.Ldloc, info.Value); 571 EmitConstant(bucket[0].Constant); 572 _ilg.Emit(OpCodes.Beq, bucket[0].Label); 573 return; 574 } 575 576 // 577 // If we're switching off of Int64/UInt64, we need more guards here 578 // because we'll have to narrow the switch value to an Int32, and 579 // we can't do that unless the value is in the right range. 580 // 581 Label? after = null; 582 if (info.Is64BitSwitch) 583 { 584 after = _ilg.DefineLabel(); 585 _ilg.Emit(OpCodes.Ldloc, info.Value); 586 EmitConstant(bucket.Last().Constant); 587 _ilg.Emit(info.IsUnsigned ? OpCodes.Bgt_Un : OpCodes.Bgt, after.Value); 588 _ilg.Emit(OpCodes.Ldloc, info.Value); 589 EmitConstant(bucket[0].Constant); 590 _ilg.Emit(info.IsUnsigned ? OpCodes.Blt_Un : OpCodes.Blt, after.Value); 591 } 592 593 _ilg.Emit(OpCodes.Ldloc, info.Value); 594 595 // Normalize key 596 decimal key = bucket[0].Key; 597 if (key != 0) 598 { 599 EmitConstant(bucket[0].Constant); 600 _ilg.Emit(OpCodes.Sub); 601 } 602 603 if (info.Is64BitSwitch) 604 { 605 _ilg.Emit(OpCodes.Conv_I4); 606 } 607 608 // Collect labels 609 int len = (int)(bucket[bucket.Count - 1].Key - bucket[0].Key + 1); 610 Label[] jmpLabels = new Label[len]; 611 612 // Initialize all labels to the default 613 int slot = 0; 614 foreach (SwitchLabel label in bucket) 615 { 616 while (key++ != label.Key) 617 { 618 jmpLabels[slot++] = info.Default; 619 } 620 jmpLabels[slot++] = label.Label; 621 } 622 623 // check we used all keys and filled all slots 624 Debug.Assert(key == bucket[bucket.Count - 1].Key + 1); 625 Debug.Assert(slot == jmpLabels.Length); 626 627 // Finally, emit the switch instruction 628 _ilg.Emit(OpCodes.Switch, jmpLabels); 629 630 if (info.Is64BitSwitch) 631 { 632 _ilg.MarkLabel(after.Value); 633 } 634 } 635 TryEmitHashtableSwitch(SwitchExpression node, CompilationFlags flags)636 private bool TryEmitHashtableSwitch(SwitchExpression node, CompilationFlags flags) 637 { 638 // If we have a comparison other than string equality, bail 639 if (node.Comparison != String_op_Equality_String_String && node.Comparison != String_Equals_String_String) 640 { 641 return false; 642 } 643 644 // All test values must be constant. 645 int tests = 0; 646 foreach (SwitchCase c in node.Cases) 647 { 648 foreach (Expression t in c.TestValues) 649 { 650 if (!(t is ConstantExpression)) 651 { 652 return false; 653 } 654 tests++; 655 } 656 } 657 658 // Must have >= 7 labels for it to be worth it. 659 if (tests < 7) 660 { 661 return false; 662 } 663 664 // If we're in a DynamicMethod, we could just build the dictionary 665 // immediately. But that would cause the two code paths to be more 666 // different than they really need to be. 667 var initializers = new List<ElementInit>(tests); 668 var cases = new ArrayBuilder<SwitchCase>(node.Cases.Count); 669 670 int nullCase = -1; 671 MethodInfo add = DictionaryOfStringInt32_Add_String_Int32; 672 for (int i = 0, n = node.Cases.Count; i < n; i++) 673 { 674 foreach (ConstantExpression t in node.Cases[i].TestValues) 675 { 676 if (t.Value != null) 677 { 678 initializers.Add(Expression.ElementInit(add, new TrueReadOnlyCollection<Expression>(t, Utils.Constant(i)))); 679 } 680 else 681 { 682 nullCase = i; 683 } 684 } 685 cases.UncheckedAdd(Expression.SwitchCase(node.Cases[i].Body, new TrueReadOnlyCollection<Expression>(Utils.Constant(i)))); 686 } 687 688 // Create the field to hold the lazily initialized dictionary 689 MemberExpression dictField = CreateLazyInitializedField<Dictionary<string, int>>("dictionarySwitch"); 690 691 // If we happen to initialize it twice (multithreaded case), it's 692 // not the end of the world. The C# compiler does better here by 693 // emitting a volatile access to the field. 694 Expression dictInit = Expression.Condition( 695 Expression.Equal(dictField, Expression.Constant(null, dictField.Type)), 696 Expression.Assign( 697 dictField, 698 Expression.ListInit( 699 Expression.New( 700 DictionaryOfStringInt32_Ctor_Int32, 701 new TrueReadOnlyCollection<Expression>( 702 Utils.Constant(initializers.Count) 703 ) 704 ), 705 initializers 706 ) 707 ), 708 dictField 709 ); 710 711 // 712 // Create a tree like: 713 // 714 // switchValue = switchValueExpression; 715 // if (switchValue == null) { 716 // switchIndex = nullCase; 717 // } else { 718 // if (_dictField == null) { 719 // _dictField = new Dictionary<string, int>(count) { { ... }, ... }; 720 // } 721 // if (!_dictField.TryGetValue(switchValue, out switchIndex)) { 722 // switchIndex = -1; 723 // } 724 // } 725 // switch (switchIndex) { 726 // case 0: ... 727 // case 1: ... 728 // ... 729 // default: 730 // } 731 // 732 ParameterExpression switchValue = Expression.Variable(typeof(string), "switchValue"); 733 ParameterExpression switchIndex = Expression.Variable(typeof(int), "switchIndex"); 734 BlockExpression reduced = Expression.Block( 735 new TrueReadOnlyCollection<ParameterExpression>(switchIndex, switchValue), 736 new TrueReadOnlyCollection<Expression>( 737 Expression.Assign(switchValue, node.SwitchValue), 738 Expression.IfThenElse( 739 Expression.Equal(switchValue, Expression.Constant(null, typeof(string))), 740 Expression.Assign(switchIndex, Utils.Constant(nullCase)), 741 Expression.IfThenElse( 742 Expression.Call(dictInit, "TryGetValue", null, switchValue, switchIndex), 743 Utils.Empty, 744 Expression.Assign(switchIndex, Utils.Constant(-1)) 745 ) 746 ), 747 Expression.Switch(node.Type, switchIndex, node.DefaultBody, null, cases.ToReadOnly()) 748 ) 749 ); 750 751 EmitExpression(reduced, flags); 752 return true; 753 } 754 755 #endregion 756 CheckRethrow()757 private void CheckRethrow() 758 { 759 // Rethrow is only valid inside a catch. 760 for (LabelScopeInfo j = _labelBlock; j != null; j = j.Parent) 761 { 762 if (j.Kind == LabelScopeKind.Catch) 763 { 764 return; 765 } 766 else if (j.Kind == LabelScopeKind.Finally) 767 { 768 // Rethrow from inside finally is not verifiable 769 break; 770 } 771 } 772 throw Error.RethrowRequiresCatch(); 773 } 774 775 #region TryStatement 776 CheckTry()777 private void CheckTry() 778 { 779 // Try inside a filter is not verifiable 780 for (LabelScopeInfo j = _labelBlock; j != null; j = j.Parent) 781 { 782 if (j.Kind == LabelScopeKind.Filter) 783 { 784 throw Error.TryNotAllowedInFilter(); 785 } 786 } 787 } 788 EmitSaveExceptionOrPop(CatchBlock cb)789 private void EmitSaveExceptionOrPop(CatchBlock cb) 790 { 791 if (cb.Variable != null) 792 { 793 // If the variable is present, store the exception 794 // in the variable. 795 _scope.EmitSet(cb.Variable); 796 } 797 else 798 { 799 // Otherwise, pop it off the stack. 800 _ilg.Emit(OpCodes.Pop); 801 } 802 } 803 EmitTryExpression(Expression expr)804 private void EmitTryExpression(Expression expr) 805 { 806 var node = (TryExpression)expr; 807 808 CheckTry(); 809 810 //****************************************************************** 811 // 1. ENTERING TRY 812 //****************************************************************** 813 814 PushLabelBlock(LabelScopeKind.Try); 815 _ilg.BeginExceptionBlock(); 816 817 //****************************************************************** 818 // 2. Emit the try statement body 819 //****************************************************************** 820 821 EmitExpression(node.Body); 822 823 Type tryType = node.Type; 824 LocalBuilder value = null; 825 if (tryType != typeof(void)) 826 { 827 //store the value of the try body 828 value = GetLocal(tryType); 829 _ilg.Emit(OpCodes.Stloc, value); 830 } 831 //****************************************************************** 832 // 3. Emit the catch blocks 833 //****************************************************************** 834 835 foreach (CatchBlock cb in node.Handlers) 836 { 837 PushLabelBlock(LabelScopeKind.Catch); 838 839 // Begin the strongly typed exception block 840 if (cb.Filter == null) 841 { 842 _ilg.BeginCatchBlock(cb.Test); 843 } 844 else 845 { 846 _ilg.BeginExceptFilterBlock(); 847 } 848 849 EnterScope(cb); 850 851 EmitCatchStart(cb); 852 853 // 854 // Emit the catch block body 855 // 856 EmitExpression(cb.Body); 857 if (tryType != typeof(void)) 858 { 859 //store the value of the catch block body 860 _ilg.Emit(OpCodes.Stloc, value); 861 } 862 863 ExitScope(cb); 864 865 PopLabelBlock(LabelScopeKind.Catch); 866 } 867 868 //****************************************************************** 869 // 4. Emit the finally block 870 //****************************************************************** 871 872 if (node.Finally != null || node.Fault != null) 873 { 874 PushLabelBlock(LabelScopeKind.Finally); 875 876 if (node.Finally != null) 877 { 878 _ilg.BeginFinallyBlock(); 879 } 880 else 881 { 882 _ilg.BeginFaultBlock(); 883 } 884 885 // Emit the body 886 EmitExpressionAsVoid(node.Finally ?? node.Fault); 887 888 _ilg.EndExceptionBlock(); 889 PopLabelBlock(LabelScopeKind.Finally); 890 } 891 else 892 { 893 _ilg.EndExceptionBlock(); 894 } 895 896 if (tryType != typeof(void)) 897 { 898 _ilg.Emit(OpCodes.Ldloc, value); 899 FreeLocal(value); 900 } 901 PopLabelBlock(LabelScopeKind.Try); 902 } 903 904 /// <summary> 905 /// Emits the start of a catch block. The exception value that is provided by the 906 /// CLR is stored in the variable specified by the catch block or popped if no 907 /// variable is provided. 908 /// </summary> EmitCatchStart(CatchBlock cb)909 private void EmitCatchStart(CatchBlock cb) 910 { 911 if (cb.Filter == null) 912 { 913 EmitSaveExceptionOrPop(cb); 914 return; 915 } 916 917 // emit filter block. Filter blocks are untyped so we need to do 918 // the type check ourselves. 919 Label endFilter = _ilg.DefineLabel(); 920 Label rightType = _ilg.DefineLabel(); 921 922 // skip if it's not our exception type, but save 923 // the exception if it is so it's available to the 924 // filter 925 _ilg.Emit(OpCodes.Isinst, cb.Test); 926 _ilg.Emit(OpCodes.Dup); 927 _ilg.Emit(OpCodes.Brtrue, rightType); 928 _ilg.Emit(OpCodes.Pop); 929 _ilg.Emit(OpCodes.Ldc_I4_0); 930 _ilg.Emit(OpCodes.Br, endFilter); 931 932 // it's our type, save it and emit the filter. 933 _ilg.MarkLabel(rightType); 934 EmitSaveExceptionOrPop(cb); 935 PushLabelBlock(LabelScopeKind.Filter); 936 EmitExpression(cb.Filter); 937 PopLabelBlock(LabelScopeKind.Filter); 938 939 // begin the catch, clear the exception, we've 940 // already saved it 941 _ilg.MarkLabel(endFilter); 942 _ilg.BeginCatchBlock(exceptionType: null); 943 _ilg.Emit(OpCodes.Pop); 944 } 945 946 #endregion 947 } 948 } 949