|
/* ****************************************************************************
*
* 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 SILVERLIGHT
using System.Core;
#endif
#if CLR2
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);
}
/// <summary>
/// Gets the common test test value type of the SwitchExpression.
/// </summary>
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<SwitchLabel> 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<List<SwitchLabel>> buckets) {
while (buckets.Count > 1) {
List<SwitchLabel> first = buckets[buckets.Count - 2];
List<SwitchLabel> 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<List<SwitchLabel>> buckets, SwitchLabel key) {
if (buckets.Count > 0) {
List<SwitchLabel> 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<SwitchLabel> { 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<decimal>();
var keys = new List<SwitchLabel>();
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<List<SwitchLabel>>();
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);
}
/// <summary>
/// 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.
/// </summary>
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<List<SwitchLabel>> 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<SwitchLabel> 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<ElementInit>(tests);
var cases = new List<SwitchCase>(node.Cases.Count);
int nullCase = -1;
MethodInfo add = typeof(Dictionary<string, int>).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<Dictionary<string, int>>("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<string, int>).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<string, int>(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);
}
/// <summary>
/// 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.
/// </summary>
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
}
}
|