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