summaryrefslogtreecommitdiff
path: root/ICSharpCode.Decompiler/ILAst
diff options
context:
space:
mode:
Diffstat (limited to 'ICSharpCode.Decompiler/ILAst')
-rw-r--r--ICSharpCode.Decompiler/ILAst/AsyncDecompiler.cs704
-rw-r--r--ICSharpCode.Decompiler/ILAst/DefaultDictionary.cs128
-rw-r--r--ICSharpCode.Decompiler/ILAst/GotoRemoval.cs319
-rw-r--r--ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs839
-rw-r--r--ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs988
-rw-r--r--ICSharpCode.Decompiler/ILAst/ILAstTypes.cs601
-rw-r--r--ICSharpCode.Decompiler/ILAst/ILCodes.cs490
-rw-r--r--ICSharpCode.Decompiler/ILAst/ILInlining.cs524
-rw-r--r--ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs543
-rw-r--r--ICSharpCode.Decompiler/ILAst/LiftedOperators.cs528
-rw-r--r--ICSharpCode.Decompiler/ILAst/LoopsAndConditions.cs443
-rw-r--r--ICSharpCode.Decompiler/ILAst/PatternMatching.cs177
-rw-r--r--ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs1103
-rw-r--r--ICSharpCode.Decompiler/ILAst/SimpleControlFlow.cs376
-rw-r--r--ICSharpCode.Decompiler/ILAst/StateRange.cs312
-rw-r--r--ICSharpCode.Decompiler/ILAst/SymbolicExecution.cs157
-rw-r--r--ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs1294
-rw-r--r--ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs635
18 files changed, 10161 insertions, 0 deletions
diff --git a/ICSharpCode.Decompiler/ILAst/AsyncDecompiler.cs b/ICSharpCode.Decompiler/ILAst/AsyncDecompiler.cs
new file mode 100644
index 00000000..801673f9
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/AsyncDecompiler.cs
@@ -0,0 +1,704 @@
+// Copyright (c) 2012 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ /// <summary>
+ /// Decompiler step for C# 5 async/await.
+ /// </summary>
+ internal class AsyncDecompiler
+ {
+ public static bool IsCompilerGeneratedStateMachine(TypeDefinition type)
+ {
+ if (!(type.DeclaringType != null && type.IsCompilerGenerated()))
+ return false;
+ foreach (TypeReference i in type.Interfaces) {
+ if (i.Namespace == "System.Runtime.CompilerServices" && i.Name == "IAsyncStateMachine")
+ return true;
+ }
+ return false;
+ }
+
+ enum AsyncMethodType
+ {
+ Void,
+ Task,
+ TaskOfT
+ }
+
+ DecompilerContext context;
+
+ // These fields are set by MatchTaskCreationPattern()
+ AsyncMethodType methodType;
+ int initialState;
+ TypeDefinition stateMachineStruct;
+ MethodDefinition moveNextMethod;
+ FieldDefinition builderField;
+ FieldDefinition stateField;
+ Dictionary<FieldDefinition, ILVariable> fieldToParameterMap = new Dictionary<FieldDefinition, ILVariable>();
+ ILVariable cachedStateVar;
+
+ // These fields are set by AnalyzeMoveNext()
+ int finalState = -2;
+ ILTryCatchBlock mainTryCatch;
+ ILLabel setResultAndExitLabel;
+ ILLabel exitLabel;
+ ILExpression resultExpr;
+
+ #region RunStep1() method
+ public static void RunStep1(DecompilerContext context, ILBlock method)
+ {
+ if (!context.Settings.AsyncAwait)
+ return; // abort if async decompilation is disabled
+ var yrd = new AsyncDecompiler();
+ yrd.context = context;
+ if (!yrd.MatchTaskCreationPattern(method))
+ return;
+ #if DEBUG
+ if (Debugger.IsAttached) {
+ yrd.Run();
+ } else {
+ #endif
+ try {
+ yrd.Run();
+ } catch (SymbolicAnalysisFailedException) {
+ return;
+ }
+ #if DEBUG
+ }
+ #endif
+ context.CurrentMethodIsAsync = true;
+
+ method.Body.Clear();
+ method.EntryGoto = null;
+ method.Body.AddRange(yrd.newTopLevelBody);
+ ILAstOptimizer.RemoveRedundantCode(method);
+ }
+
+ void Run()
+ {
+ AnalyzeMoveNext();
+ ValidateCatchBlock(mainTryCatch.CatchBlocks[0]);
+ AnalyzeStateMachine(mainTryCatch.TryBlock);
+ // AnalyzeStateMachine invokes ConvertBody
+ MarkGeneratedVariables();
+ YieldReturnDecompiler.TranslateFieldsToLocalAccess(newTopLevelBody, fieldToParameterMap);
+ }
+ #endregion
+
+ #region MatchTaskCreationPattern
+ bool MatchTaskCreationPattern(ILBlock method)
+ {
+ if (method.Body.Count < 5)
+ return false;
+ // Check the second-to-last instruction (the start call) first, as we can get the most information from that
+ MethodReference startMethod;
+ ILExpression loadStartTarget, loadStartArgument;
+ // call(AsyncTaskMethodBuilder::Start, ldloca(builder), ldloca(stateMachine))
+ if (!method.Body[method.Body.Count - 2].Match(ILCode.Call, out startMethod, out loadStartTarget, out loadStartArgument))
+ return false;
+ if (startMethod.Name != "Start" || startMethod.DeclaringType == null || startMethod.DeclaringType.Namespace != "System.Runtime.CompilerServices")
+ return false;
+ switch (startMethod.DeclaringType.Name) {
+ case "AsyncTaskMethodBuilder`1":
+ methodType = AsyncMethodType.TaskOfT;
+ break;
+ case "AsyncTaskMethodBuilder":
+ methodType = AsyncMethodType.Task;
+ break;
+ case "AsyncVoidMethodBuilder":
+ methodType = AsyncMethodType.Void;
+ break;
+ default:
+ return false;
+ }
+ ILVariable stateMachineVar, builderVar;
+ if (!loadStartTarget.Match(ILCode.Ldloca, out builderVar))
+ return false;
+ if (!loadStartArgument.Match(ILCode.Ldloca, out stateMachineVar))
+ return false;
+
+ stateMachineStruct = stateMachineVar.Type.ResolveWithinSameModule();
+ if (stateMachineStruct == null || !stateMachineStruct.IsValueType)
+ return false;
+ moveNextMethod = stateMachineStruct.Methods.FirstOrDefault(f => f.Name == "MoveNext");
+ if (moveNextMethod == null)
+ return false;
+
+ // Check third-to-last instruction (copy of builder):
+ // stloc(builder, ldfld(StateMachine::<>t__builder, ldloca(stateMachine)))
+ ILExpression loadBuilderExpr;
+ if (!method.Body[method.Body.Count - 3].MatchStloc(builderVar, out loadBuilderExpr))
+ return false;
+ FieldReference builderFieldRef;
+ ILExpression loadStateMachineForBuilderExpr;
+ if (!loadBuilderExpr.Match(ILCode.Ldfld, out builderFieldRef, out loadStateMachineForBuilderExpr))
+ return false;
+ if (!(loadStateMachineForBuilderExpr.MatchLdloca(stateMachineVar) || loadStateMachineForBuilderExpr.MatchLdloc(stateMachineVar)))
+ return false;
+ builderField = builderFieldRef.ResolveWithinSameModule();
+ if (builderField == null)
+ return false;
+
+ // Check the last instruction (ret)
+ if (methodType == AsyncMethodType.Void) {
+ if (!method.Body[method.Body.Count - 1].Match(ILCode.Ret))
+ return false;
+ } else {
+ // ret(call(AsyncTaskMethodBuilder::get_Task, ldflda(StateMachine::<>t__builder, ldloca(stateMachine))))
+ ILExpression returnValue;
+ if (!method.Body[method.Body.Count - 1].Match(ILCode.Ret, out returnValue))
+ return false;
+ MethodReference getTaskMethod;
+ ILExpression builderExpr;
+ if (!returnValue.Match(ILCode.Call, out getTaskMethod, out builderExpr))
+ return false;
+ ILExpression loadStateMachineForBuilderExpr2;
+ FieldReference builderField2;
+ if (!builderExpr.Match(ILCode.Ldflda, out builderField2, out loadStateMachineForBuilderExpr2))
+ return false;
+ if (builderField2.ResolveWithinSameModule() != builderField || !loadStateMachineForBuilderExpr2.MatchLdloca(stateMachineVar))
+ return false;
+ }
+
+ // Check the last field assignment - this should be the state field
+ ILExpression initialStateExpr;
+ if (!MatchStFld(method.Body[method.Body.Count - 4], stateMachineVar, out stateField, out initialStateExpr))
+ return false;
+ if (!initialStateExpr.Match(ILCode.Ldc_I4, out initialState))
+ return false;
+ if (initialState != -1)
+ return false;
+
+ // Check the second-to-last field assignment - this should be the builder field
+ FieldDefinition builderField3;
+ ILExpression builderInitialization;
+ if (!MatchStFld(method.Body[method.Body.Count - 5], stateMachineVar, out builderField3, out builderInitialization))
+ return false;
+ MethodReference createMethodRef;
+ if (builderField3 != builderField || !builderInitialization.Match(ILCode.Call, out createMethodRef))
+ return false;
+ if (createMethodRef.Name != "Create")
+ return false;
+
+ for (int i = 0; i < method.Body.Count - 5; i++) {
+ FieldDefinition field;
+ ILExpression fieldInit;
+ if (!MatchStFld(method.Body[i], stateMachineVar, out field, out fieldInit))
+ return false;
+ ILVariable v;
+ if (!fieldInit.Match(ILCode.Ldloc, out v))
+ return false;
+ if (!v.IsParameter)
+ return false;
+ fieldToParameterMap[field] = v;
+ }
+
+ return true;
+ }
+
+ static bool MatchStFld(ILNode stfld, ILVariable stateMachineVar, out FieldDefinition field, out ILExpression expr)
+ {
+ field = null;
+ FieldReference fieldRef;
+ ILExpression ldloca;
+ if (!stfld.Match(ILCode.Stfld, out fieldRef, out ldloca, out expr))
+ return false;
+ field = fieldRef.ResolveWithinSameModule();
+ return field != null && ldloca.MatchLdloca(stateMachineVar);
+ }
+ #endregion
+
+ #region Analyze MoveNext
+ void AnalyzeMoveNext()
+ {
+ ILBlock ilMethod = CreateILAst(moveNextMethod);
+
+ int startIndex;
+ if (ilMethod.Body.Count == 6) {
+ startIndex = 0;
+ } else if (ilMethod.Body.Count == 7) {
+ // stloc(cachedState, ldfld(valuetype StateMachineStruct::<>1__state, ldloc(this)))
+ ILExpression cachedStateInit;
+ if (!ilMethod.Body[0].Match(ILCode.Stloc, out cachedStateVar, out cachedStateInit))
+ throw new SymbolicAnalysisFailedException();
+ ILExpression instanceExpr;
+ FieldReference loadedField;
+ if (!cachedStateInit.Match(ILCode.Ldfld, out loadedField, out instanceExpr) || loadedField.ResolveWithinSameModule() != stateField || !instanceExpr.MatchThis())
+ throw new SymbolicAnalysisFailedException();
+ startIndex = 1;
+ } else {
+ throw new SymbolicAnalysisFailedException();
+ }
+
+ mainTryCatch = ilMethod.Body[startIndex + 0] as ILTryCatchBlock;
+ if (mainTryCatch == null || mainTryCatch.CatchBlocks.Count != 1)
+ throw new SymbolicAnalysisFailedException();
+ if (mainTryCatch.FaultBlock != null || mainTryCatch.FinallyBlock != null)
+ throw new SymbolicAnalysisFailedException();
+
+ setResultAndExitLabel = ilMethod.Body[startIndex + 1] as ILLabel;
+ if (setResultAndExitLabel == null)
+ throw new SymbolicAnalysisFailedException();
+
+ if (!MatchStateAssignment(ilMethod.Body[startIndex + 2], out finalState))
+ throw new SymbolicAnalysisFailedException();
+
+ // call(AsyncTaskMethodBuilder`1::SetResult, ldflda(StateMachine::<>t__builder, ldloc(this)), ldloc(<>t__result))
+ MethodReference setResultMethod;
+ ILExpression builderExpr;
+ if (methodType == AsyncMethodType.TaskOfT) {
+ if (!ilMethod.Body[startIndex + 3].Match(ILCode.Call, out setResultMethod, out builderExpr, out resultExpr))
+ throw new SymbolicAnalysisFailedException();
+ } else {
+ if (!ilMethod.Body[startIndex + 3].Match(ILCode.Call, out setResultMethod, out builderExpr))
+ throw new SymbolicAnalysisFailedException();
+ }
+ if (!(setResultMethod.Name == "SetResult" && IsBuilderFieldOnThis(builderExpr)))
+ throw new SymbolicAnalysisFailedException();
+
+ exitLabel = ilMethod.Body[startIndex + 4] as ILLabel;
+ if (exitLabel == null)
+ throw new SymbolicAnalysisFailedException();
+ }
+
+ /// <summary>
+ /// Creates ILAst for the specified method, optimized up to before the 'YieldReturn' step.
+ /// </summary>
+ ILBlock CreateILAst(MethodDefinition method)
+ {
+ if (method == null || !method.HasBody)
+ throw new SymbolicAnalysisFailedException();
+
+ ILBlock ilMethod = new ILBlock();
+ ILAstBuilder astBuilder = new ILAstBuilder();
+ ilMethod.Body = astBuilder.Build(method, true, context);
+ ILAstOptimizer optimizer = new ILAstOptimizer();
+ optimizer.Optimize(context, ilMethod, ILAstOptimizationStep.YieldReturn);
+ return ilMethod;
+ }
+
+ void ValidateCatchBlock(ILTryCatchBlock.CatchBlock catchBlock)
+ {
+ if (catchBlock.ExceptionType == null || catchBlock.ExceptionType.Name != "Exception")
+ throw new SymbolicAnalysisFailedException();
+ if (catchBlock.Body.Count != 3)
+ throw new SymbolicAnalysisFailedException();
+ int stateID;
+ if (!(MatchStateAssignment(catchBlock.Body[0], out stateID) && stateID == finalState))
+ throw new SymbolicAnalysisFailedException();
+ MethodReference setExceptionMethod;
+ ILExpression builderExpr, exceptionExpr;
+ if (!catchBlock.Body[1].Match(ILCode.Call, out setExceptionMethod, out builderExpr, out exceptionExpr))
+ throw new SymbolicAnalysisFailedException();
+ if (!(setExceptionMethod.Name == "SetException" && IsBuilderFieldOnThis(builderExpr) && exceptionExpr.MatchLdloc(catchBlock.ExceptionVariable)))
+ throw new SymbolicAnalysisFailedException();
+
+ ILLabel label;
+ if (!(catchBlock.Body[2].Match(ILCode.Leave, out label) && label == exitLabel))
+ throw new SymbolicAnalysisFailedException();
+ }
+
+ bool IsBuilderFieldOnThis(ILExpression builderExpr)
+ {
+ // ldflda(StateMachine::<>t__builder, ldloc(this))
+ FieldReference fieldRef;
+ ILExpression target;
+ return builderExpr.Match(ILCode.Ldflda, out fieldRef, out target)
+ && fieldRef.ResolveWithinSameModule() == builderField
+ && target.MatchThis();
+ }
+
+ bool MatchStateAssignment(ILNode stfld, out int stateID)
+ {
+ // stfld(StateMachine::<>1__state, ldloc(this), ldc.i4(stateId))
+ stateID = 0;
+ FieldReference fieldRef;
+ ILExpression target, val;
+ if (stfld.Match(ILCode.Stfld, out fieldRef, out target, out val)) {
+ return fieldRef.ResolveWithinSameModule() == stateField
+ && target.MatchThis()
+ && val.Match(ILCode.Ldc_I4, out stateID);
+ }
+ return false;
+ }
+
+ bool MatchRoslynStateAssignment(List<ILNode> block, int index, out int stateID)
+ {
+ // v = ldc.i4(stateId)
+ // stloc(cachedState, v)
+ // stfld(StateMachine::<>1__state, ldloc(this), v)
+ stateID = 0;
+ if (index < 0)
+ return false;
+ ILVariable v;
+ ILExpression val;
+ if (!block[index].Match(ILCode.Stloc, out v, out val) || !val.Match(ILCode.Ldc_I4, out stateID))
+ return false;
+ ILExpression loadV;
+ if (!block[index + 1].MatchStloc(cachedStateVar, out loadV) || !loadV.MatchLdloc(v))
+ return false;
+ ILExpression target;
+ FieldReference fieldRef;
+ if (block[index + 2].Match(ILCode.Stfld, out fieldRef, out target, out loadV)) {
+ return fieldRef.ResolveWithinSameModule() == stateField
+ && target.MatchThis()
+ && loadV.MatchLdloc(v);
+ }
+ return false;
+ }
+ #endregion
+
+ #region AnalyzeStateMachine
+ ILVariable doFinallyBodies;
+ List<ILNode> newTopLevelBody;
+
+ void AnalyzeStateMachine(ILBlock block)
+ {
+ var body = block.Body;
+ if (body.Count == 0)
+ throw new SymbolicAnalysisFailedException();
+ if (DetectDoFinallyBodies(body)) {
+ body.RemoveAt(0);
+ if (body.Count == 0)
+ throw new SymbolicAnalysisFailedException();
+ }
+ StateRangeAnalysis rangeAnalysis = new StateRangeAnalysis(body[0], StateRangeAnalysisMode.AsyncMoveNext, stateField, cachedStateVar);
+ int bodyLength = block.Body.Count;
+ int pos = rangeAnalysis.AssignStateRanges(body, bodyLength);
+ rangeAnalysis.EnsureLabelAtPos(body, ref pos, ref bodyLength);
+
+ var labelStateRangeMapping = rangeAnalysis.CreateLabelRangeMapping(body, pos, bodyLength);
+ newTopLevelBody = ConvertBody(body, pos, bodyLength, labelStateRangeMapping);
+ newTopLevelBody.Insert(0, MakeGoTo(labelStateRangeMapping, initialState));
+ newTopLevelBody.Add(setResultAndExitLabel);
+ if (methodType == AsyncMethodType.TaskOfT) {
+ newTopLevelBody.Add(new ILExpression(ILCode.Ret, null, resultExpr));
+ } else {
+ newTopLevelBody.Add(new ILExpression(ILCode.Ret, null));
+ }
+ }
+
+ bool DetectDoFinallyBodies(List<ILNode> body)
+ {
+ ILVariable v;
+ ILExpression initExpr;
+ if (!body[0].Match(ILCode.Stloc, out v, out initExpr))
+ return false;
+ int initialValue;
+ if (!(initExpr.Match(ILCode.Ldc_I4, out initialValue) && initialValue == 1))
+ return false;
+ doFinallyBodies = v;
+ return true;
+ }
+ #endregion
+
+ #region ConvertBody
+ ILExpression MakeGoTo(LabelRangeMapping mapping, int state)
+ {
+ foreach (var pair in mapping) {
+ if (pair.Value.Contains(state))
+ return new ILExpression(ILCode.Br, pair.Key);
+ }
+ throw new SymbolicAnalysisFailedException();
+ }
+
+ List<ILNode> ConvertBody(List<ILNode> body, int startPos, int bodyLength, LabelRangeMapping mapping)
+ {
+ List<ILNode> newBody = new List<ILNode>();
+ // Copy all instructions from the old body to newBody.
+ for (int pos = startPos; pos < bodyLength; pos++) {
+ ILTryCatchBlock tryCatchBlock = body[pos] as ILTryCatchBlock;
+ ILExpression expr = body[pos] as ILExpression;
+ if (expr != null && expr.Code == ILCode.Leave && expr.Operand == exitLabel) {
+ ILVariable awaiterVar;
+ FieldDefinition awaiterField;
+ int targetStateID;
+ HandleAwait(newBody, out awaiterVar, out awaiterField, out targetStateID);
+ MarkAsGeneratedVariable(awaiterVar);
+ newBody.Add(new ILExpression(ILCode.Await, null, new ILExpression(ILCode.Ldloca, awaiterVar)));
+ newBody.Add(MakeGoTo(mapping, targetStateID));
+ } else if (tryCatchBlock != null) {
+ ILTryCatchBlock newTryCatchBlock = new ILTryCatchBlock();
+ var tryBody = tryCatchBlock.TryBlock.Body;
+ if (tryBody.Count == 0)
+ throw new SymbolicAnalysisFailedException();
+ StateRangeAnalysis rangeAnalysis = new StateRangeAnalysis(tryBody[0], StateRangeAnalysisMode.AsyncMoveNext, stateField);
+ int tryBodyLength = tryBody.Count;
+ int posInTryBody = rangeAnalysis.AssignStateRanges(tryBody, tryBodyLength);
+ rangeAnalysis.EnsureLabelAtPos(tryBody, ref posInTryBody, ref tryBodyLength);
+
+ var mappingInTryBlock = rangeAnalysis.CreateLabelRangeMapping(tryBody, posInTryBody, tryBodyLength);
+ var newTryBody = ConvertBody(tryBody, posInTryBody, tryBodyLength, mappingInTryBlock);
+ newTryBody.Insert(0, MakeGoTo(mappingInTryBlock, initialState));
+
+ // If there's a label at the beginning of the state dispatcher, copy that
+ if (posInTryBody > 0 && tryBody.FirstOrDefault() is ILLabel)
+ newTryBody.Insert(0, tryBody.First());
+
+ newTryCatchBlock.TryBlock = new ILBlock(newTryBody);
+ newTryCatchBlock.CatchBlocks = new List<ILTryCatchBlock.CatchBlock>(tryCatchBlock.CatchBlocks);
+ newTryCatchBlock.FaultBlock = tryCatchBlock.FaultBlock;
+ if (tryCatchBlock.FinallyBlock != null)
+ newTryCatchBlock.FinallyBlock = new ILBlock(ConvertFinally(tryCatchBlock.FinallyBlock.Body));
+
+ newBody.Add(newTryCatchBlock);
+ } else {
+ newBody.Add(body[pos]);
+ }
+ }
+ return newBody;
+ }
+
+ List<ILNode> ConvertFinally(List<ILNode> body)
+ {
+ List<ILNode> newBody = new List<ILNode>(body);
+ if (newBody.Count == 0)
+ return newBody;
+ ILLabel endFinallyLabel;
+ ILExpression ceqExpr;
+ if (newBody[0].Match(ILCode.Brtrue, out endFinallyLabel, out ceqExpr)) {
+ ILExpression condition;
+ if (MatchLogicNot(ceqExpr, out condition)) {
+ if (condition.MatchLdloc(doFinallyBodies)) {
+ newBody.RemoveAt(0);
+ } else if (condition.Code == ILCode.Clt && condition.Arguments[0].MatchLdloc(cachedStateVar) && condition.Arguments[1].MatchLdcI4(0)) {
+ newBody.RemoveAt(0);
+ }
+ }
+ }
+ return newBody;
+ }
+
+ bool MatchLogicNot(ILExpression expr, out ILExpression arg)
+ {
+ ILExpression loadZero;
+ object unused;
+ if (expr.Match(ILCode.Ceq, out unused, out arg, out loadZero)) {
+ int num;
+ return loadZero.Match(ILCode.Ldc_I4, out num) && num == 0;
+ }
+ return expr.Match(ILCode.LogicNot, out arg);
+ }
+
+ void HandleAwait(List<ILNode> newBody, out ILVariable awaiterVar, out FieldDefinition awaiterField, out int targetStateID)
+ {
+ // Handle the instructions prior to the exit out of the method to detect what is being awaited.
+ // (analyses the last instructions in newBody and removes the analyzed instructions from newBody)
+
+ if (doFinallyBodies != null) {
+ // stloc(<>t__doFinallyBodies, ldc.i4(0))
+ ILExpression dfbInitExpr;
+ if (!newBody.LastOrDefault().MatchStloc(doFinallyBodies, out dfbInitExpr))
+ throw new SymbolicAnalysisFailedException();
+ int val;
+ if (!(dfbInitExpr.Match(ILCode.Ldc_I4, out val) && val == 0))
+ throw new SymbolicAnalysisFailedException();
+ newBody.RemoveAt(newBody.Count - 1); // remove doFinallyBodies assignment
+ }
+
+ // call(AsyncTaskMethodBuilder::AwaitUnsafeOnCompleted, ldflda(StateMachine::<>t__builder, ldloc(this)), ldloca(CS$0$0001), ldloc(this))
+ ILExpression callAwaitUnsafeOnCompleted = newBody.LastOrDefault() as ILExpression;
+ newBody.RemoveAt(newBody.Count - 1); // remove AwaitUnsafeOnCompleted call
+ if (callAwaitUnsafeOnCompleted == null || callAwaitUnsafeOnCompleted.Code != ILCode.Call)
+ throw new SymbolicAnalysisFailedException();
+ string methodName = ((MethodReference)callAwaitUnsafeOnCompleted.Operand).Name;
+ if (methodName != "AwaitUnsafeOnCompleted" && methodName != "AwaitOnCompleted")
+ throw new SymbolicAnalysisFailedException();
+ if (callAwaitUnsafeOnCompleted.Arguments.Count != 3)
+ throw new SymbolicAnalysisFailedException();
+ if (!callAwaitUnsafeOnCompleted.Arguments[1].Match(ILCode.Ldloca, out awaiterVar))
+ throw new SymbolicAnalysisFailedException();
+
+ // stfld(StateMachine::<>u__$awaiter6, ldloc(this), ldloc(CS$0$0001))
+ FieldReference awaiterFieldRef;
+ ILExpression loadThis, loadAwaiterVar;
+ if (!newBody.LastOrDefault().Match(ILCode.Stfld, out awaiterFieldRef, out loadThis, out loadAwaiterVar))
+ throw new SymbolicAnalysisFailedException();
+ newBody.RemoveAt(newBody.Count - 1); // remove awaiter field assignment
+ awaiterField = awaiterFieldRef.ResolveWithinSameModule();
+ if (!(awaiterField != null && loadThis.MatchThis() && loadAwaiterVar.MatchLdloc(awaiterVar)))
+ throw new SymbolicAnalysisFailedException();
+
+ // stfld(StateMachine::<>1__state, ldloc(this), ldc.i4(0))
+ if (MatchStateAssignment(newBody.LastOrDefault(), out targetStateID))
+ newBody.RemoveAt(newBody.Count - 1); // remove awaiter field assignment
+ else if (MatchRoslynStateAssignment(newBody, newBody.Count - 3, out targetStateID))
+ newBody.RemoveRange(newBody.Count - 3, 3); // remove awaiter field assignment
+ }
+ #endregion
+
+ #region MarkGeneratedVariables
+ int smallestGeneratedVariableIndex = int.MaxValue;
+
+ void MarkAsGeneratedVariable(ILVariable v)
+ {
+ if (v.OriginalVariable != null && v.OriginalVariable.Index >= 0) {
+ smallestGeneratedVariableIndex = Math.Min(smallestGeneratedVariableIndex, v.OriginalVariable.Index);
+ }
+ }
+
+ void MarkGeneratedVariables()
+ {
+ var expressions = new ILBlock(newTopLevelBody).GetSelfAndChildrenRecursive<ILExpression>();
+ foreach (var v in expressions.Select(e => e.Operand).OfType<ILVariable>()) {
+ if (v.OriginalVariable != null && v.OriginalVariable.Index >= smallestGeneratedVariableIndex)
+ v.IsGenerated = true;
+ }
+ }
+ #endregion
+
+ #region RunStep2() method
+ public static void RunStep2(DecompilerContext context, ILBlock method)
+ {
+ if (context.CurrentMethodIsAsync) {
+ Step2(method.Body);
+ ILAstOptimizer.RemoveRedundantCode(method);
+ // Repeat the inlining/copy propagation optimization because the conversion of field access
+ // to local variables can open up additional inlining possibilities.
+ ILInlining inlining = new ILInlining(method);
+ inlining.InlineAllVariables();
+ inlining.CopyPropagation();
+ }
+ }
+
+ static void Step2(List<ILNode> body)
+ {
+ for (int pos = 0; pos < body.Count; pos++) {
+ ILTryCatchBlock tc = body[pos] as ILTryCatchBlock;
+ if (tc != null) {
+ Step2(tc.TryBlock.Body);
+ } else {
+ Step2(body, ref pos);
+ }
+ }
+ }
+
+ static bool Step2(List<ILNode> body, ref int pos)
+ {
+ // stloc(CS$0$0001, callvirt(class System.Threading.Tasks.Task`1<bool>::GetAwaiter, awaiterExpr)
+ // brtrue(IL_7C, call(valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<bool>::get_IsCompleted, ldloca(CS$0$0001)))
+ // await(ldloca(CS$0$0001))
+ // ...
+ // IL_7C:
+ // arg_8B_0 = call(valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<bool>::GetResult, ldloca(CS$0$0001))
+ // initobj(valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<bool>, ldloca(CS$0$0001))
+
+ ILExpression loadAwaiter;
+ ILVariable awaiterVar;
+ if (!body[pos].Match(ILCode.Await, out loadAwaiter))
+ return false;
+ if (!loadAwaiter.Match(ILCode.Ldloca, out awaiterVar))
+ return false;
+
+ ILVariable stackVar;
+ ILExpression stackExpr;
+ while (pos >= 1 && body[pos - 1].Match(ILCode.Stloc, out stackVar, out stackExpr))
+ pos--;
+
+ // stloc(CS$0$0001, callvirt(class System.Threading.Tasks.Task`1<bool>::GetAwaiter, awaiterExpr)
+ ILExpression getAwaiterCall;
+ if (!(pos >= 2 && body[pos - 2].MatchStloc(awaiterVar, out getAwaiterCall)))
+ return false;
+ MethodReference getAwaiterMethod;
+ ILExpression awaitedExpr;
+ if (!(getAwaiterCall.Match(ILCode.Call, out getAwaiterMethod, out awaitedExpr) || getAwaiterCall.Match(ILCode.Callvirt, out getAwaiterMethod, out awaitedExpr)))
+ return false;
+
+ if (awaitedExpr.Code == ILCode.AddressOf) {
+ // remove 'AddressOf()' when calling GetAwaiter() on a value type
+ awaitedExpr = awaitedExpr.Arguments[0];
+ }
+
+ // brtrue(IL_7C, call(valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<bool>::get_IsCompleted, ldloca(CS$0$0001)))
+ ILLabel label;
+ ILExpression getIsCompletedCall;
+ if (!(pos >= 1 && body[pos - 1].Match(ILCode.Brtrue, out label, out getIsCompletedCall)))
+ return false;
+
+ int labelPos = body.IndexOf(label);
+ if (labelPos < pos)
+ return false;
+ for (int i = pos + 1; i < labelPos; i++) {
+ // validate that we aren't deleting any unexpected instructions -
+ // between the await and the label, there should only be the stack, awaiter and state logic
+ ILExpression expr = body[i] as ILExpression;
+ if (expr == null)
+ return false;
+ switch (expr.Code) {
+ case ILCode.Stloc:
+ case ILCode.Initobj:
+ case ILCode.Stfld:
+ case ILCode.Await:
+ // e.g.
+ // stloc(CS$0$0001, ldfld(StateMachine::<>u__$awaitere, ldloc(this)))
+ // initobj(valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<bool>, ldloca(CS$0$0002_66))
+ // stfld('<AwaitInLoopCondition>d__d'::<>u__$awaitere, ldloc(this), ldloc(CS$0$0002_66))
+ // stfld('<AwaitInLoopCondition>d__d'::<>1__state, ldloc(this), ldc.i4(-1))
+ break;
+ default:
+ return false;
+ }
+ }
+ if (labelPos + 1 >= body.Count)
+ return false;
+ ILExpression resultAssignment = body[labelPos + 1] as ILExpression;
+ ILVariable resultVar;
+ ILExpression getResultCall;
+ bool isResultAssignment = resultAssignment.Match(ILCode.Stloc, out resultVar, out getResultCall);
+ if (!isResultAssignment)
+ getResultCall = resultAssignment;
+ if (!(getResultCall.Operand is MethodReference && ((MethodReference)getResultCall.Operand).Name == "GetResult"))
+ return false;
+
+ pos -= 2; // also delete 'stloc', 'brtrue' and 'await'
+ body.RemoveRange(pos, labelPos - pos);
+ Debug.Assert(body[pos] == label);
+
+ pos++;
+ if (isResultAssignment) {
+ Debug.Assert(body[pos] == resultAssignment);
+ resultAssignment.Arguments[0] = new ILExpression(ILCode.Await, null, awaitedExpr);
+ } else {
+ body[pos] = new ILExpression(ILCode.Await, null, awaitedExpr);
+ }
+
+ // if the awaiter variable is cleared out in the next instruction, remove that instruction
+ if (IsVariableReset(body.ElementAtOrDefault(pos + 1), awaiterVar)) {
+ body.RemoveAt(pos + 1);
+ }
+
+ return true;
+ }
+
+ static bool IsVariableReset(ILNode expr, ILVariable variable)
+ {
+ object unused;
+ ILExpression ldloca;
+ return expr.Match(ILCode.Initobj, out unused, out ldloca) && ldloca.MatchLdloca(variable);
+ }
+ #endregion
+ }
+}
diff --git a/ICSharpCode.Decompiler/ILAst/DefaultDictionary.cs b/ICSharpCode.Decompiler/ILAst/DefaultDictionary.cs
new file mode 100644
index 00000000..1e359a3d
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/DefaultDictionary.cs
@@ -0,0 +1,128 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ /// <summary>
+ /// Dictionary with default values.
+ /// </summary>
+ internal sealed class DefaultDictionary<TKey, TValue> : IDictionary<TKey, TValue>
+ {
+ readonly IDictionary<TKey, TValue> dict;
+ readonly Func<TKey, TValue> defaultProvider;
+
+ public DefaultDictionary(TValue defaultValue, IDictionary<TKey, TValue> dictionary = null)
+ : this(key => defaultValue, dictionary)
+ {
+ }
+
+ public DefaultDictionary(Func<TKey, TValue> defaultProvider = null, IDictionary<TKey, TValue> dictionary = null)
+ {
+ dict = dictionary ?? new Dictionary<TKey, TValue>();
+ this.defaultProvider = defaultProvider ?? (key => default(TValue));
+ }
+
+ public TValue this[TKey key] {
+ get {
+ TValue val;
+ if (dict.TryGetValue(key, out val))
+ return val;
+ else
+ return dict[key] = defaultProvider(key);
+ }
+ set {
+ dict[key] = value;
+ }
+ }
+
+ public ICollection<TKey> Keys {
+ get { return dict.Keys; }
+ }
+
+ public ICollection<TValue> Values {
+ get { return dict.Values; }
+ }
+
+ public int Count {
+ get { return dict.Count; }
+ }
+
+ bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly {
+ get { return false; }
+ }
+
+ public bool ContainsKey(TKey key)
+ {
+ return dict.ContainsKey(key);
+ }
+
+ public void Add(TKey key, TValue value)
+ {
+ dict.Add(key, value);
+ }
+
+ public bool Remove(TKey key)
+ {
+ return dict.Remove(key);
+ }
+
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ return dict.TryGetValue(key, out value);
+ }
+
+ void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
+ {
+ dict.Add(item);
+ }
+
+ public void Clear()
+ {
+ dict.Clear();
+ }
+
+ bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
+ {
+ return dict.Contains(item);
+ }
+
+ void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
+ {
+ dict.CopyTo(array, arrayIndex);
+ }
+
+ bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
+ {
+ return dict.Remove(item);
+ }
+
+ IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
+ {
+ return dict.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return dict.GetEnumerator();
+ }
+ }
+} \ No newline at end of file
diff --git a/ICSharpCode.Decompiler/ILAst/GotoRemoval.cs b/ICSharpCode.Decompiler/ILAst/GotoRemoval.cs
new file mode 100644
index 00000000..fafe13e1
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/GotoRemoval.cs
@@ -0,0 +1,319 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ public class GotoRemoval
+ {
+ Dictionary<ILNode, ILNode> parent = new Dictionary<ILNode, ILNode>();
+ Dictionary<ILNode, ILNode> nextSibling = new Dictionary<ILNode, ILNode>();
+
+ public void RemoveGotos(ILBlock method)
+ {
+ // Build the navigation data
+ parent[method] = null;
+ foreach (ILNode node in method.GetSelfAndChildrenRecursive<ILNode>()) {
+ ILNode previousChild = null;
+ foreach (ILNode child in node.GetChildren()) {
+ if (parent.ContainsKey(child))
+ throw new Exception("The following expression is linked from several locations: " + child.ToString());
+ parent[child] = node;
+ if (previousChild != null)
+ nextSibling[previousChild] = child;
+ previousChild = child;
+ }
+ if (previousChild != null)
+ nextSibling[previousChild] = null;
+ }
+
+ // Simplify gotos
+ bool modified;
+ do {
+ modified = false;
+ foreach (ILExpression gotoExpr in method.GetSelfAndChildrenRecursive<ILExpression>(e => e.Code == ILCode.Br || e.Code == ILCode.Leave)) {
+ modified |= TrySimplifyGoto(gotoExpr);
+ }
+ } while(modified);
+
+ RemoveRedundantCode(method);
+ }
+
+ public static void RemoveRedundantCode(ILBlock method)
+ {
+ // Remove dead lables and nops
+ HashSet<ILLabel> liveLabels = new HashSet<ILLabel>(method.GetSelfAndChildrenRecursive<ILExpression>(e => e.IsBranch()).SelectMany(e => e.GetBranchTargets()));
+ foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) {
+ block.Body = block.Body.Where(n => !n.Match(ILCode.Nop) && !(n is ILLabel && !liveLabels.Contains((ILLabel)n))).ToList();
+ }
+
+ // Remove redundant continue
+ foreach(ILWhileLoop loop in method.GetSelfAndChildrenRecursive<ILWhileLoop>()) {
+ var body = loop.BodyBlock.Body;
+ if (body.Count > 0 && body.Last().Match(ILCode.LoopContinue)) {
+ body.RemoveAt(body.Count - 1);
+ }
+ }
+
+ // Remove redundant break at the end of case
+ // Remove redundant case blocks altogether
+ foreach(ILSwitch ilSwitch in method.GetSelfAndChildrenRecursive<ILSwitch>()) {
+ foreach(ILBlock ilCase in ilSwitch.CaseBlocks) {
+ Debug.Assert(ilCase.EntryGoto == null);
+
+ int count = ilCase.Body.Count;
+ if (count >= 2) {
+ if (ilCase.Body[count - 2].IsUnconditionalControlFlow() &&
+ ilCase.Body[count - 1].Match(ILCode.LoopOrSwitchBreak))
+ {
+ ilCase.Body.RemoveAt(count - 1);
+ }
+ }
+ }
+
+ var defaultCase = ilSwitch.CaseBlocks.SingleOrDefault(cb => cb.Values == null);
+ // If there is no default block, remove empty case blocks
+ if (defaultCase == null || (defaultCase.Body.Count == 1 && defaultCase.Body.Single().Match(ILCode.LoopOrSwitchBreak))) {
+ ilSwitch.CaseBlocks.RemoveAll(b => b.Body.Count == 1 && b.Body.Single().Match(ILCode.LoopOrSwitchBreak));
+ }
+ }
+
+ // Remove redundant return at the end of method
+ if (method.Body.Count > 0 && method.Body.Last().Match(ILCode.Ret) && ((ILExpression)method.Body.Last()).Arguments.Count == 0) {
+ method.Body.RemoveAt(method.Body.Count - 1);
+ }
+
+ // Remove unreachable return statements
+ bool modified = false;
+ foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) {
+ for (int i = 0; i < block.Body.Count - 1;) {
+ if (block.Body[i].IsUnconditionalControlFlow() && block.Body[i+1].Match(ILCode.Ret)) {
+ modified = true;
+ block.Body.RemoveAt(i+1);
+ } else {
+ i++;
+ }
+ }
+ }
+ if (modified) {
+ // More removals might be possible
+ new GotoRemoval().RemoveGotos(method);
+ }
+ }
+
+ IEnumerable<ILNode> GetParents(ILNode node)
+ {
+ ILNode current = node;
+ while(true) {
+ current = parent[current];
+ if (current == null)
+ yield break;
+ yield return current;
+ }
+ }
+
+ bool TrySimplifyGoto(ILExpression gotoExpr)
+ {
+ Debug.Assert(gotoExpr.Code == ILCode.Br || gotoExpr.Code == ILCode.Leave);
+ Debug.Assert(gotoExpr.Prefixes == null);
+ Debug.Assert(gotoExpr.Operand != null);
+
+ ILNode target = Enter(gotoExpr, new HashSet<ILNode>());
+ if (target == null)
+ return false;
+
+ // The gotoExper is marked as visited because we do not want to
+ // walk over node which we plan to modify
+
+ // The simulated path always has to start in the same try-block
+ // in other for the same finally blocks to be executed.
+
+ if (target == Exit(gotoExpr, new HashSet<ILNode>() { gotoExpr })) {
+ gotoExpr.Code = ILCode.Nop;
+ gotoExpr.Operand = null;
+ if (target is ILExpression)
+ ((ILExpression)target).ILRanges.AddRange(gotoExpr.ILRanges);
+ gotoExpr.ILRanges.Clear();
+ return true;
+ }
+
+ ILNode breakBlock = GetParents(gotoExpr).FirstOrDefault(n => n is ILWhileLoop || n is ILSwitch);
+ if (breakBlock != null && target == Exit(breakBlock, new HashSet<ILNode>() { gotoExpr })) {
+ gotoExpr.Code = ILCode.LoopOrSwitchBreak;
+ gotoExpr.Operand = null;
+ return true;
+ }
+
+ ILNode continueBlock = GetParents(gotoExpr).FirstOrDefault(n => n is ILWhileLoop);
+ if (continueBlock != null && target == Enter(continueBlock, new HashSet<ILNode>() { gotoExpr })) {
+ gotoExpr.Code = ILCode.LoopContinue;
+ gotoExpr.Operand = null;
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Get the first expression to be excecuted if the instruction pointer is at the start of the given node.
+ /// Try blocks may not be entered in any way. If possible, the try block is returned as the node to be executed.
+ /// </summary>
+ ILNode Enter(ILNode node, HashSet<ILNode> visitedNodes)
+ {
+ if (node == null)
+ throw new ArgumentNullException();
+
+ if (!visitedNodes.Add(node))
+ return null; // Infinite loop
+
+ ILLabel label = node as ILLabel;
+ if (label != null) {
+ return Exit(label, visitedNodes);
+ }
+
+ ILExpression expr = node as ILExpression;
+ if (expr != null) {
+ if (expr.Code == ILCode.Br || expr.Code == ILCode.Leave) {
+ ILLabel target = (ILLabel)expr.Operand;
+ // Early exit - same try-block
+ if (GetParents(expr).OfType<ILTryCatchBlock>().FirstOrDefault() == GetParents(target).OfType<ILTryCatchBlock>().FirstOrDefault())
+ return Enter(target, visitedNodes);
+ // Make sure we are not entering any try-block
+ var srcTryBlocks = GetParents(expr).OfType<ILTryCatchBlock>().Reverse().ToList();
+ var dstTryBlocks = GetParents(target).OfType<ILTryCatchBlock>().Reverse().ToList();
+ // Skip blocks that we are already in
+ int i = 0;
+ while(i < srcTryBlocks.Count && i < dstTryBlocks.Count && srcTryBlocks[i] == dstTryBlocks[i]) i++;
+ if (i == dstTryBlocks.Count) {
+ return Enter(target, visitedNodes);
+ } else {
+ ILTryCatchBlock dstTryBlock = dstTryBlocks[i];
+ // Check that the goto points to the start
+ ILTryCatchBlock current = dstTryBlock;
+ while(current != null) {
+ foreach(ILNode n in current.TryBlock.Body) {
+ if (n is ILLabel) {
+ if (n == target)
+ return dstTryBlock;
+ } else if (!n.Match(ILCode.Nop)) {
+ current = n as ILTryCatchBlock;
+ break;
+ }
+ }
+ }
+ return null;
+ }
+ } else if (expr.Code == ILCode.Nop) {
+ return Exit(expr, visitedNodes);
+ } else if (expr.Code == ILCode.LoopOrSwitchBreak) {
+ ILNode breakBlock = GetParents(expr).First(n => n is ILWhileLoop || n is ILSwitch);
+ return Exit(breakBlock, new HashSet<ILNode>() { expr });
+ } else if (expr.Code == ILCode.LoopContinue) {
+ ILNode continueBlock = GetParents(expr).First(n => n is ILWhileLoop);
+ return Enter(continueBlock, new HashSet<ILNode>() { expr });
+ } else {
+ return expr;
+ }
+ }
+
+ ILBlock block = node as ILBlock;
+ if (block != null) {
+ if (block.EntryGoto != null) {
+ return Enter(block.EntryGoto, visitedNodes);
+ } else if (block.Body.Count > 0) {
+ return Enter(block.Body[0], visitedNodes);
+ } else {
+ return Exit(block, visitedNodes);
+ }
+ }
+
+ ILCondition cond = node as ILCondition;
+ if (cond != null) {
+ return cond.Condition;
+ }
+
+ ILWhileLoop loop = node as ILWhileLoop;
+ if (loop != null) {
+ if (loop.Condition != null) {
+ return loop.Condition;
+ } else {
+ return Enter(loop.BodyBlock, visitedNodes);
+ }
+ }
+
+ ILTryCatchBlock tryCatch = node as ILTryCatchBlock;
+ if (tryCatch != null) {
+ return tryCatch;
+ }
+
+ ILSwitch ilSwitch = node as ILSwitch;
+ if (ilSwitch != null) {
+ return ilSwitch.Condition;
+ }
+
+ throw new NotSupportedException(node.GetType().ToString());
+ }
+
+ /// <summary>
+ /// Get the first expression to be excecuted if the instruction pointer is at the end of the given node
+ /// </summary>
+ ILNode Exit(ILNode node, HashSet<ILNode> visitedNodes)
+ {
+ if (node == null)
+ throw new ArgumentNullException();
+
+ ILNode nodeParent = parent[node];
+ if (nodeParent == null)
+ return null; // Exited main body
+
+ if (nodeParent is ILBlock) {
+ ILNode nextNode = nextSibling[node];
+ if (nextNode != null) {
+ return Enter(nextNode, visitedNodes);
+ } else {
+ return Exit(nodeParent, visitedNodes);
+ }
+ }
+
+ if (nodeParent is ILCondition) {
+ return Exit(nodeParent, visitedNodes);
+ }
+
+ if (nodeParent is ILTryCatchBlock) {
+ // Finally blocks are completely ignored.
+ // We rely on the fact that try blocks can not be entered.
+ return Exit(nodeParent, visitedNodes);
+ }
+
+ if (nodeParent is ILSwitch) {
+ return null; // Implicit exit from switch is not allowed
+ }
+
+ if (nodeParent is ILWhileLoop) {
+ return Enter(nodeParent, visitedNodes);
+ }
+
+ throw new NotSupportedException(nodeParent.GetType().ToString());
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs
new file mode 100644
index 00000000..21b2b150
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs
@@ -0,0 +1,839 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using Cecil = Mono.Cecil;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ /// <summary>
+ /// Converts stack-based bytecode to variable-based bytecode by calculating use-define chains
+ /// </summary>
+ public class ILAstBuilder
+ {
+ /// <summary> Immutable </summary>
+ struct StackSlot
+ {
+ public readonly ByteCode[] Definitions; // Reaching definitions of this stack slot
+ public readonly ILVariable LoadFrom; // Variable used for storage of the value
+
+ public StackSlot(ByteCode[] definitions, ILVariable loadFrom)
+ {
+ Definitions = definitions;
+ LoadFrom = loadFrom;
+ }
+
+ public static StackSlot[] ModifyStack(StackSlot[] stack, int popCount, int pushCount, ByteCode pushDefinition)
+ {
+ StackSlot[] newStack = new StackSlot[stack.Length - popCount + pushCount];
+ Array.Copy(stack, newStack, stack.Length - popCount);
+ for (int i = stack.Length - popCount; i < newStack.Length; i++) {
+ newStack[i] = new StackSlot(new [] { pushDefinition }, null);
+ }
+ return newStack;
+ }
+ }
+
+ /// <summary> Immutable </summary>
+ struct VariableSlot
+ {
+ public readonly ByteCode[] Definitions; // Reaching deinitions of this variable
+ public readonly bool UnknownDefinition; // Used for initial state and exceptional control flow
+
+ static readonly VariableSlot UnknownInstance = new VariableSlot(new ByteCode[0], true);
+
+ public VariableSlot(ByteCode[] definitions, bool unknownDefinition)
+ {
+ Definitions = definitions;
+ UnknownDefinition = unknownDefinition;
+ }
+
+ public static VariableSlot[] CloneVariableState(VariableSlot[] state)
+ {
+ VariableSlot[] clone = new VariableSlot[state.Length];
+ Array.Copy(state, clone, state.Length);
+ return clone;
+ }
+
+ public static VariableSlot[] MakeUknownState(int varCount)
+ {
+ VariableSlot[] unknownVariableState = new VariableSlot[varCount];
+ for (int i = 0; i < unknownVariableState.Length; i++) {
+ unknownVariableState[i] = UnknownInstance;
+ }
+ return unknownVariableState;
+ }
+ }
+
+ sealed class ByteCode
+ {
+ public ILLabel Label; // Non-null only if needed
+ public int Offset;
+ public int EndOffset;
+ public ILCode Code;
+ public object Operand;
+ public int? PopCount; // Null means pop all
+ public int PushCount;
+ public string Name { get { return "IL_" + Offset.ToString("X2"); } }
+ public ByteCode Next;
+ public Instruction[] Prefixes; // Non-null only if needed
+ public StackSlot[] StackBefore; // Unique per bytecode; not shared
+ public VariableSlot[] VariablesBefore; // Unique per bytecode; not shared
+ public List<ILVariable> StoreTo; // Store result of instruction to those AST variables
+
+ public bool IsVariableDefinition {
+ get {
+ return (Code == ILCode.Stloc) || (Code == ILCode.Ldloca && Next != null && Next.Code == ILCode.Initobj);
+ }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ // Label
+ sb.Append(Name);
+ sb.Append(':');
+ if (Label != null)
+ sb.Append('*');
+
+ // Name
+ sb.Append(' ');
+ if (Prefixes != null) {
+ foreach (var prefix in Prefixes) {
+ sb.Append(prefix.OpCode.Name);
+ sb.Append(' ');
+ }
+ }
+ sb.Append(Code.GetName());
+
+ if (Operand != null) {
+ sb.Append(' ');
+ if (Operand is Instruction) {
+ sb.Append("IL_" + ((Instruction)Operand).Offset.ToString("X2"));
+ } else if (Operand is Instruction[]) {
+ foreach(Instruction inst in (Instruction[])Operand) {
+ sb.Append("IL_" + inst.Offset.ToString("X2"));
+ sb.Append(" ");
+ }
+ } else if (Operand is ILLabel) {
+ sb.Append(((ILLabel)Operand).Name);
+ } else if (Operand is ILLabel[]) {
+ foreach(ILLabel label in (ILLabel[])Operand) {
+ sb.Append(label.Name);
+ sb.Append(" ");
+ }
+ } else {
+ sb.Append(Operand.ToString());
+ }
+ }
+
+ if (StackBefore != null) {
+ sb.Append(" StackBefore={");
+ bool first = true;
+ foreach (StackSlot slot in StackBefore) {
+ if (!first) sb.Append(",");
+ bool first2 = true;
+ foreach(ByteCode defs in slot.Definitions) {
+ if (!first2) sb.Append("|");
+ sb.AppendFormat("IL_{0:X2}", defs.Offset);
+ first2 = false;
+ }
+ first = false;
+ }
+ sb.Append("}");
+ }
+
+ if (StoreTo != null && StoreTo.Count > 0) {
+ sb.Append(" StoreTo={");
+ bool first = true;
+ foreach (ILVariable stackVar in StoreTo) {
+ if (!first) sb.Append(",");
+ sb.Append(stackVar.Name);
+ first = false;
+ }
+ sb.Append("}");
+ }
+
+ if (VariablesBefore != null) {
+ sb.Append(" VarsBefore={");
+ bool first = true;
+ foreach (VariableSlot varSlot in VariablesBefore) {
+ if (!first) sb.Append(",");
+ if (varSlot.UnknownDefinition) {
+ sb.Append("?");
+ } else {
+ bool first2 = true;
+ foreach (ByteCode storedBy in varSlot.Definitions) {
+ if (!first2) sb.Append("|");
+ sb.AppendFormat("IL_{0:X2}", storedBy.Offset);
+ first2 = false;
+ }
+ }
+ first = false;
+ }
+ sb.Append("}");
+ }
+
+ return sb.ToString();
+ }
+ }
+
+ MethodDefinition methodDef;
+ bool optimize;
+
+ // Virtual instructions to load exception on stack
+ Dictionary<ExceptionHandler, ByteCode> ldexceptions = new Dictionary<ExceptionHandler, ByteCode>();
+
+ DecompilerContext context;
+
+ public List<ILNode> Build(MethodDefinition methodDef, bool optimize, DecompilerContext context)
+ {
+ this.methodDef = methodDef;
+ this.optimize = optimize;
+ this.context = context;
+
+ if (methodDef.Body.Instructions.Count == 0) return new List<ILNode>();
+
+ List<ByteCode> body = StackAnalysis(methodDef);
+
+ List<ILNode> ast = ConvertToAst(body, new HashSet<ExceptionHandler>(methodDef.Body.ExceptionHandlers));
+
+ return ast;
+ }
+
+ static int offsetIdx;
+ List<ByteCode> StackAnalysis(MethodDefinition methodDef)
+ {
+ Dictionary<Instruction, ByteCode> instrToByteCode = new Dictionary<Instruction, ByteCode>();
+
+ // Create temporary structure for the stack analysis
+ List<ByteCode> body = new List<ByteCode>(methodDef.Body.Instructions.Count);
+ List<Instruction> prefixes = null;
+ foreach(Instruction inst in methodDef.Body.Instructions) {
+ if (inst.OpCode.OpCodeType == OpCodeType.Prefix) {
+ if (prefixes == null)
+ prefixes = new List<Instruction>(1);
+ prefixes.Add(inst);
+ continue;
+ }
+ ILCode code = (ILCode)inst.OpCode.Code;
+ object operand = inst.Operand;
+ ILCodeUtil.ExpandMacro(ref code, ref operand, methodDef.Body);
+ ByteCode byteCode = new ByteCode() {
+ Offset = inst.Offset,
+ EndOffset = inst.Next != null ? inst.Next.Offset : methodDef.Body.CodeSize,
+ Code = code,
+ Operand = operand,
+ PopCount = inst.GetPopDelta(methodDef),
+ PushCount = inst.GetPushDelta()
+ };
+ if (prefixes != null) {
+ instrToByteCode[prefixes[0]] = byteCode;
+ byteCode.Offset = prefixes[0].Offset;
+ byteCode.Prefixes = prefixes.ToArray();
+ prefixes = null;
+ } else {
+ instrToByteCode[inst] = byteCode;
+ }
+ body.Add(byteCode);
+ }
+ for (int i = 0; i < body.Count - 1; i++) {
+ body[i].Next = body[i + 1];
+ }
+
+ Stack<ByteCode> agenda = new Stack<ByteCode>();
+
+ int varCount = methodDef.Body.Variables.Count;
+
+ var exceptionHandlerStarts = new HashSet<ByteCode>(methodDef.Body.ExceptionHandlers.Select(eh => instrToByteCode[eh.HandlerStart]));
+
+ // Add known states
+ if(methodDef.Body.HasExceptionHandlers) {
+ foreach(ExceptionHandler ex in methodDef.Body.ExceptionHandlers) {
+ ByteCode handlerStart = instrToByteCode[ex.HandlerStart];
+ handlerStart.StackBefore = new StackSlot[0];
+ handlerStart.VariablesBefore = VariableSlot.MakeUknownState(varCount);
+ if (ex.HandlerType == ExceptionHandlerType.Catch || ex.HandlerType == ExceptionHandlerType.Filter) {
+ // Catch and Filter handlers start with the exeption on the stack
+ ByteCode ldexception = new ByteCode() {
+ Code = ILCode.Ldexception,
+ Operand = ex.CatchType,
+ PopCount = 0,
+ PushCount = 1
+ };
+ ldexceptions[ex] = ldexception;
+ handlerStart.StackBefore = new StackSlot[] { new StackSlot(new [] { ldexception }, null) };
+ }
+ agenda.Push(handlerStart);
+
+ if (ex.HandlerType == ExceptionHandlerType.Filter)
+ {
+ ByteCode filterStart = instrToByteCode[ex.FilterStart];
+ ByteCode ldexception = new ByteCode() {
+ Code = ILCode.Ldexception,
+ Operand = ex.CatchType,
+ PopCount = 0,
+ PushCount = 1
+ };
+ // TODO: ldexceptions[ex] = ldexception;
+ filterStart.StackBefore = new StackSlot[] { new StackSlot(new [] { ldexception }, null) };
+ filterStart.VariablesBefore = VariableSlot.MakeUknownState(varCount);
+ agenda.Push(filterStart);
+ }
+ }
+ }
+
+ body[0].StackBefore = new StackSlot[0];
+ body[0].VariablesBefore = VariableSlot.MakeUknownState(varCount);
+ agenda.Push(body[0]);
+
+ // Process agenda
+ while(agenda.Count > 0) {
+ ByteCode byteCode = agenda.Pop();
+
+ // Calculate new stack
+ StackSlot[] newStack = StackSlot.ModifyStack(byteCode.StackBefore, byteCode.PopCount ?? byteCode.StackBefore.Length, byteCode.PushCount, byteCode);
+
+ // Calculate new variable state
+ VariableSlot[] newVariableState = VariableSlot.CloneVariableState(byteCode.VariablesBefore);
+ if (byteCode.IsVariableDefinition) {
+ newVariableState[((VariableReference)byteCode.Operand).Index] = new VariableSlot(new [] { byteCode }, false);
+ }
+
+ // After the leave, finally block might have touched the variables
+ if (byteCode.Code == ILCode.Leave) {
+ newVariableState = VariableSlot.MakeUknownState(varCount);
+ }
+
+ // Find all successors
+ List<ByteCode> branchTargets = new List<ByteCode>();
+ if (!byteCode.Code.IsUnconditionalControlFlow()) {
+ if (exceptionHandlerStarts.Contains(byteCode.Next)) {
+ // Do not fall though down to exception handler
+ // It is invalid IL as per ECMA-335 §12.4.2.8.1, but some obfuscators produce it
+ } else {
+ branchTargets.Add(byteCode.Next);
+ }
+ }
+ if (byteCode.Operand is Instruction[]) {
+ foreach(Instruction inst in (Instruction[])byteCode.Operand) {
+ ByteCode target = instrToByteCode[inst];
+ branchTargets.Add(target);
+ // The target of a branch must have label
+ if (target.Label == null) {
+ target.Label = new ILLabel() { Name = target.Name };
+ }
+ }
+ } else if (byteCode.Operand is Instruction) {
+ ByteCode target = instrToByteCode[(Instruction)byteCode.Operand];
+ branchTargets.Add(target);
+ // The target of a branch must have label
+ if (target.Label == null) {
+ target.Label = new ILLabel() { Name = target.Name };
+ }
+ }
+
+ // Apply the state to successors
+ foreach (ByteCode branchTarget in branchTargets) {
+ if (branchTarget.StackBefore == null && branchTarget.VariablesBefore == null) {
+ if (branchTargets.Count == 1) {
+ branchTarget.StackBefore = newStack;
+ branchTarget.VariablesBefore = newVariableState;
+ } else {
+ // Do not share data for several bytecodes
+ branchTarget.StackBefore = StackSlot.ModifyStack(newStack, 0, 0, null);
+ branchTarget.VariablesBefore = VariableSlot.CloneVariableState(newVariableState);
+ }
+ agenda.Push(branchTarget);
+ } else {
+ if (branchTarget.StackBefore.Length != newStack.Length) {
+ throw new Exception("Inconsistent stack size at " + byteCode.Name);
+ }
+
+ // Be careful not to change our new data - it might be reused for several branch targets.
+ // In general, be careful that two bytecodes never share data structures.
+
+ bool modified = false;
+
+ // Merge stacks - modify the target
+ for (int i = 0; i < newStack.Length; i++) {
+ ByteCode[] oldDefs = branchTarget.StackBefore[i].Definitions;
+ ByteCode[] newDefs = oldDefs.Union(newStack[i].Definitions);
+ if (newDefs.Length > oldDefs.Length) {
+ branchTarget.StackBefore[i] = new StackSlot(newDefs, null);
+ modified = true;
+ }
+ }
+
+ // Merge variables - modify the target
+ for (int i = 0; i < newVariableState.Length; i++) {
+ VariableSlot oldSlot = branchTarget.VariablesBefore[i];
+ VariableSlot newSlot = newVariableState[i];
+ if (!oldSlot.UnknownDefinition) {
+ if (newSlot.UnknownDefinition) {
+ branchTarget.VariablesBefore[i] = newSlot;
+ modified = true;
+ } else {
+ ByteCode[] oldDefs = oldSlot.Definitions;
+ ByteCode[] newDefs = oldDefs.Union(newSlot.Definitions);
+ if (newDefs.Length > oldDefs.Length) {
+ branchTarget.VariablesBefore[i] = new VariableSlot(newDefs, false);
+ modified = true;
+ }
+ }
+ }
+ }
+
+ if (modified) {
+ agenda.Push(branchTarget);
+ }
+ }
+ }
+ }
+
+ // Occasionally the compilers or obfuscators generate unreachable code (which might be intentionally invalid)
+ // I believe it is safe to just remove it
+ body.RemoveAll(b => b.StackBefore == null);
+
+ // Generate temporary variables to replace stack
+ foreach(ByteCode byteCode in body) {
+ int argIdx = 0;
+ int popCount = byteCode.PopCount ?? byteCode.StackBefore.Length;
+ for (int i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) {
+ var offset = byteCode.Offset != 0 ? byteCode.Offset : offsetIdx++;
+ ILVariable tmpVar = new ILVariable() { Name = string.Format("arg_{0:X2}_{1}", offset, argIdx), IsGenerated = true };
+ byteCode.StackBefore[i] = new StackSlot(byteCode.StackBefore[i].Definitions, tmpVar);
+ foreach(ByteCode pushedBy in byteCode.StackBefore[i].Definitions) {
+ if (pushedBy.StoreTo == null) {
+ pushedBy.StoreTo = new List<ILVariable>(1);
+ }
+ pushedBy.StoreTo.Add(tmpVar);
+ }
+ argIdx++;
+ }
+ }
+
+ // Try to use single temporary variable insted of several if possilbe (especially useful for dup)
+ // This has to be done after all temporary variables are assigned so we know about all loads
+ foreach(ByteCode byteCode in body) {
+ if (byteCode.StoreTo != null && byteCode.StoreTo.Count > 1) {
+ var locVars = byteCode.StoreTo;
+ // For each of the variables, find the location where it is loaded - there should be preciesly one
+ var loadedBy = locVars.Select(locVar => body.SelectMany(bc => bc.StackBefore).Single(s => s.LoadFrom == locVar)).ToList();
+ // We now know that all the variables have a single load,
+ // Let's make sure that they have also a single store - us
+ if (loadedBy.All(slot => slot.Definitions.Length == 1 && slot.Definitions[0] == byteCode)) {
+ // Great - we can reduce everything into single variable
+ var offset = byteCode.Offset != 0 ? byteCode.Offset : offsetIdx++;
+ ILVariable tmpVar = new ILVariable() { Name = string.Format("expr_{0:X2}", offset), IsGenerated = true };
+ byteCode.StoreTo = new List<ILVariable>() { tmpVar };
+ foreach(ByteCode bc in body) {
+ for (int i = 0; i < bc.StackBefore.Length; i++) {
+ // Is it one of the variable to be merged?
+ if (locVars.Contains(bc.StackBefore[i].LoadFrom)) {
+ // Replace with the new temp variable
+ bc.StackBefore[i] = new StackSlot(bc.StackBefore[i].Definitions, tmpVar);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Split and convert the normal local variables
+ ConvertLocalVariables(body);
+
+ // Convert branch targets to labels
+ foreach(ByteCode byteCode in body) {
+ if (byteCode.Operand is Instruction[]) {
+ List<ILLabel> newOperand = new List<ILLabel>();
+ foreach(Instruction target in (Instruction[])byteCode.Operand) {
+ newOperand.Add(instrToByteCode[target].Label);
+ }
+ byteCode.Operand = newOperand.ToArray();
+ } else if (byteCode.Operand is Instruction) {
+ byteCode.Operand = instrToByteCode[(Instruction)byteCode.Operand].Label;
+ }
+ }
+
+ // Convert parameters to ILVariables
+ ConvertParameters(body);
+
+ return body;
+ }
+
+ static bool IsDeterministicLdloca(ByteCode b)
+ {
+ var v = b.Operand;
+ b = b.Next;
+ if (b.Code == ILCode.Initobj) return true;
+
+ // instance method calls on value types use the variable ref deterministically
+ int stack = 1;
+ while (true) {
+ if (b.PopCount == null) return false;
+ stack -= b.PopCount.GetValueOrDefault();
+ if (stack == 0) break;
+ if (stack < 0) return false;
+ if (b.Code.IsConditionalControlFlow() || b.Code.IsUnconditionalControlFlow()) return false;
+ switch (b.Code) {
+ case ILCode.Ldloc:
+ case ILCode.Ldloca:
+ case ILCode.Stloc:
+ if (b.Operand == v) return false;
+ break;
+ }
+ stack += b.PushCount;
+ b = b.Next;
+ if (b == null) return false;
+ }
+ if (b.Code == ILCode.Ldfld || b.Code == ILCode.Stfld)
+ return true;
+ return (b.Code == ILCode.Call || b.Code == ILCode.Callvirt) && ((MethodReference)b.Operand).HasThis;
+ }
+
+ sealed class VariableInfo
+ {
+ public ILVariable Variable;
+ public List<ByteCode> Defs;
+ public List<ByteCode> Uses;
+ }
+
+ /// <summary>
+ /// If possible, separates local variables into several independent variables.
+ /// It should undo any compilers merging.
+ /// </summary>
+ void ConvertLocalVariables(List<ByteCode> body)
+ {
+ foreach(VariableDefinition varDef in methodDef.Body.Variables) {
+
+ // Find all definitions and uses of this variable
+ var defs = body.Where(b => b.Operand == varDef && b.IsVariableDefinition).ToList();
+ var uses = body.Where(b => b.Operand == varDef && !b.IsVariableDefinition).ToList();
+
+ List<VariableInfo> newVars;
+
+ // If the variable is pinned, use single variable.
+ // If any of the uses is from unknown definition, use single variable
+ // If any of the uses is ldloca with a nondeterministic usage pattern, use single variable
+ if (!optimize || varDef.IsPinned || uses.Any(b => b.VariablesBefore[varDef.Index].UnknownDefinition || (b.Code == ILCode.Ldloca && !IsDeterministicLdloca(b)))) {
+ newVars = new List<VariableInfo>(1) { new VariableInfo() {
+ Variable = new ILVariable() {
+ Name = string.IsNullOrEmpty(varDef.Name) ? "var_" + varDef.Index : varDef.Name,
+ Type = varDef.IsPinned ? ((PinnedType)varDef.VariableType).ElementType : varDef.VariableType,
+ OriginalVariable = varDef
+ },
+ Defs = defs,
+ Uses = uses
+ }};
+ } else {
+ // Create a new variable for each definition
+ newVars = defs.Select(def => new VariableInfo() {
+ Variable = new ILVariable() {
+ Name = (string.IsNullOrEmpty(varDef.Name) ? "var_" + varDef.Index : varDef.Name) + "_" + def.Offset.ToString("X2"),
+ Type = varDef.VariableType,
+ OriginalVariable = varDef
+ },
+ Defs = new List<ByteCode>() { def },
+ Uses = new List<ByteCode>()
+ }).ToList();
+
+ // VB.NET uses the 'init' to allow use of uninitialized variables.
+ // We do not really care about them too much - if the original variable
+ // was uninitialized at that point it means that no store was called and
+ // thus all our new variables must be uninitialized as well.
+ // So it does not matter which one we load.
+
+ // TODO: We should add explicit initialization so that C# code compiles.
+ // Remember to handle cases where one path inits the variable, but other does not.
+
+ // Add loads to the data structure; merge variables if necessary
+ foreach(ByteCode use in uses) {
+ ByteCode[] useDefs = use.VariablesBefore[varDef.Index].Definitions;
+ if (useDefs.Length == 1) {
+ VariableInfo newVar = newVars.Single(v => v.Defs.Contains(useDefs[0]));
+ newVar.Uses.Add(use);
+ } else {
+ List<VariableInfo> mergeVars = newVars.Where(v => v.Defs.Intersect(useDefs).Any()).ToList();
+ VariableInfo mergedVar = new VariableInfo() {
+ Variable = mergeVars[0].Variable,
+ Defs = mergeVars.SelectMany(v => v.Defs).ToList(),
+ Uses = mergeVars.SelectMany(v => v.Uses).ToList()
+ };
+ mergedVar.Uses.Add(use);
+ newVars = newVars.Except(mergeVars).ToList();
+ newVars.Add(mergedVar);
+ }
+ }
+ }
+
+ // Set bytecode operands
+ foreach(VariableInfo newVar in newVars) {
+ foreach(ByteCode def in newVar.Defs) {
+ def.Operand = newVar.Variable;
+ }
+ foreach(ByteCode use in newVar.Uses) {
+ use.Operand = newVar.Variable;
+ }
+ }
+ }
+ }
+
+ public List<ILVariable> Parameters = new List<ILVariable>();
+
+ void ConvertParameters(List<ByteCode> body)
+ {
+ ILVariable thisParameter = null;
+ if (methodDef.HasThis) {
+ TypeReference type = methodDef.DeclaringType;
+ thisParameter = new ILVariable();
+ thisParameter.Type = type.IsValueType ? new ByReferenceType(type) : type;
+ thisParameter.Name = "this";
+ thisParameter.OriginalParameter = methodDef.Body.ThisParameter;
+ }
+ foreach (ParameterDefinition p in methodDef.Parameters) {
+ Parameters.Add(new ILVariable { Type = p.ParameterType, Name = p.Name, OriginalParameter = p });
+ }
+ if (Parameters.Count > 0 && (methodDef.IsSetter || methodDef.IsAddOn || methodDef.IsRemoveOn)) {
+ // last parameter must be 'value', so rename it
+ Parameters.Last().Name = "value";
+ }
+ foreach (ByteCode byteCode in body) {
+ ParameterDefinition p;
+ switch (byteCode.Code) {
+ case ILCode.__Ldarg:
+ p = (ParameterDefinition)byteCode.Operand;
+ byteCode.Code = ILCode.Ldloc;
+ byteCode.Operand = p.Index < 0 ? thisParameter : Parameters[p.Index];
+ break;
+ case ILCode.__Starg:
+ p = (ParameterDefinition)byteCode.Operand;
+ byteCode.Code = ILCode.Stloc;
+ byteCode.Operand = p.Index < 0 ? thisParameter : Parameters[p.Index];
+ break;
+ case ILCode.__Ldarga:
+ p = (ParameterDefinition)byteCode.Operand;
+ byteCode.Code = ILCode.Ldloca;
+ byteCode.Operand = p.Index < 0 ? thisParameter : Parameters[p.Index];
+ break;
+ }
+ }
+ if (thisParameter != null)
+ Parameters.Add(thisParameter);
+ }
+
+ List<ILNode> ConvertToAst(List<ByteCode> body, HashSet<ExceptionHandler> ehs)
+ {
+ List<ILNode> ast = new List<ILNode>();
+
+ while (ehs.Any()) {
+ ILTryCatchBlock tryCatchBlock = new ILTryCatchBlock();
+
+ // Find the first and widest scope
+ int tryStart = ehs.Min(eh => eh.TryStart.Offset);
+ int tryEnd = ehs.Where(eh => eh.TryStart.Offset == tryStart).Max(eh => eh.TryEnd.Offset);
+ var handlers = ehs.Where(eh => eh.TryStart.Offset == tryStart && eh.TryEnd.Offset == tryEnd).ToList();
+
+ // Remember that any part of the body migt have been removed due to unreachability
+
+ // Cut all instructions up to the try block
+ {
+ int tryStartIdx = 0;
+ while (tryStartIdx < body.Count && body[tryStartIdx].Offset < tryStart) tryStartIdx++;
+ ast.AddRange(ConvertToAst(body.CutRange(0, tryStartIdx)));
+ }
+
+ // Cut the try block
+ {
+ HashSet<ExceptionHandler> nestedEHs = new HashSet<ExceptionHandler>(ehs.Where(eh => (tryStart <= eh.TryStart.Offset && eh.TryEnd.Offset < tryEnd) || (tryStart < eh.TryStart.Offset && eh.TryEnd.Offset <= tryEnd)));
+ ehs.ExceptWith(nestedEHs);
+ int tryEndIdx = 0;
+ while (tryEndIdx < body.Count && body[tryEndIdx].Offset < tryEnd) tryEndIdx++;
+ tryCatchBlock.TryBlock = new ILBlock(ConvertToAst(body.CutRange(0, tryEndIdx), nestedEHs));
+ }
+
+ // Cut all handlers
+ tryCatchBlock.CatchBlocks = new List<ILTryCatchBlock.CatchBlock>();
+ foreach(ExceptionHandler eh in handlers) {
+ int handlerEndOffset = eh.HandlerEnd == null ? methodDef.Body.CodeSize : eh.HandlerEnd.Offset;
+ int startIdx = 0;
+ while (startIdx < body.Count && body[startIdx].Offset < eh.HandlerStart.Offset) startIdx++;
+ int endIdx = 0;
+ while (endIdx < body.Count && body[endIdx].Offset < handlerEndOffset) endIdx++;
+ HashSet<ExceptionHandler> nestedEHs = new HashSet<ExceptionHandler>(ehs.Where(e => (eh.HandlerStart.Offset <= e.TryStart.Offset && e.TryEnd.Offset < handlerEndOffset) || (eh.HandlerStart.Offset < e.TryStart.Offset && e.TryEnd.Offset <= handlerEndOffset)));
+ ehs.ExceptWith(nestedEHs);
+ List<ILNode> handlerAst = ConvertToAst(body.CutRange(startIdx, endIdx - startIdx), nestedEHs);
+ if (eh.HandlerType == ExceptionHandlerType.Catch) {
+ ILTryCatchBlock.CatchBlock catchBlock = new ILTryCatchBlock.CatchBlock() {
+ ExceptionType = eh.CatchType,
+ Body = handlerAst
+ };
+ // Handle the automatically pushed exception on the stack
+ ByteCode ldexception = ldexceptions[eh];
+ if (ldexception.StoreTo == null || ldexception.StoreTo.Count == 0) {
+ // Exception is not used
+ catchBlock.ExceptionVariable = null;
+ } else if (ldexception.StoreTo.Count == 1) {
+ ILExpression first = catchBlock.Body[0] as ILExpression;
+ if (first != null &&
+ first.Code == ILCode.Pop &&
+ first.Arguments[0].Code == ILCode.Ldloc &&
+ first.Arguments[0].Operand == ldexception.StoreTo[0])
+ {
+ // The exception is just poped - optimize it all away;
+ if (context.Settings.AlwaysGenerateExceptionVariableForCatchBlocks)
+ catchBlock.ExceptionVariable = new ILVariable() { Name = "ex_" + eh.HandlerStart.Offset.ToString("X2"), IsGenerated = true };
+ else
+ catchBlock.ExceptionVariable = null;
+ catchBlock.Body.RemoveAt(0);
+ } else {
+ catchBlock.ExceptionVariable = ldexception.StoreTo[0];
+ }
+ } else {
+ ILVariable exTemp = new ILVariable() { Name = "ex_" + eh.HandlerStart.Offset.ToString("X2"), IsGenerated = true };
+ catchBlock.ExceptionVariable = exTemp;
+ foreach(ILVariable storeTo in ldexception.StoreTo) {
+ catchBlock.Body.Insert(0, new ILExpression(ILCode.Stloc, storeTo, new ILExpression(ILCode.Ldloc, exTemp)));
+ }
+ }
+ tryCatchBlock.CatchBlocks.Add(catchBlock);
+ } else if (eh.HandlerType == ExceptionHandlerType.Finally) {
+ tryCatchBlock.FinallyBlock = new ILBlock(handlerAst);
+ } else if (eh.HandlerType == ExceptionHandlerType.Fault) {
+ tryCatchBlock.FaultBlock = new ILBlock(handlerAst);
+ } else {
+ // TODO: ExceptionHandlerType.Filter
+ }
+ }
+
+ ehs.ExceptWith(handlers);
+
+ ast.Add(tryCatchBlock);
+ }
+
+ // Add whatever is left
+ ast.AddRange(ConvertToAst(body));
+
+ return ast;
+ }
+
+ List<ILNode> ConvertToAst(List<ByteCode> body)
+ {
+ List<ILNode> ast = new List<ILNode>();
+
+ // Convert stack-based IL code to ILAst tree
+ foreach(ByteCode byteCode in body) {
+ ILRange ilRange = new ILRange(byteCode.Offset, byteCode.EndOffset);
+
+ if (byteCode.StackBefore == null) {
+ // Unreachable code
+ continue;
+ }
+
+ ILExpression expr = new ILExpression(byteCode.Code, byteCode.Operand);
+ expr.ILRanges.Add(ilRange);
+ if (byteCode.Prefixes != null && byteCode.Prefixes.Length > 0) {
+ ILExpressionPrefix[] prefixes = new ILExpressionPrefix[byteCode.Prefixes.Length];
+ for (int i = 0; i < prefixes.Length; i++) {
+ prefixes[i] = new ILExpressionPrefix((ILCode)byteCode.Prefixes[i].OpCode.Code, byteCode.Prefixes[i].Operand);
+ }
+ expr.Prefixes = prefixes;
+ }
+
+ // Label for this instruction
+ if (byteCode.Label != null) {
+ ast.Add(byteCode.Label);
+ }
+
+ // Reference arguments using temporary variables
+ int popCount = byteCode.PopCount ?? byteCode.StackBefore.Length;
+ for (int i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) {
+ StackSlot slot = byteCode.StackBefore[i];
+ expr.Arguments.Add(new ILExpression(ILCode.Ldloc, slot.LoadFrom));
+ }
+
+ // Store the result to temporary variable(s) if needed
+ if (byteCode.StoreTo == null || byteCode.StoreTo.Count == 0) {
+ ast.Add(expr);
+ } else if (byteCode.StoreTo.Count == 1) {
+ ast.Add(new ILExpression(ILCode.Stloc, byteCode.StoreTo[0], expr));
+ } else {
+ var offset = byteCode.Offset != 0 ? byteCode.Offset : offsetIdx++;
+ ILVariable tmpVar = new ILVariable() { Name = "expr_" + offset.ToString("X2"), IsGenerated = true };
+ ast.Add(new ILExpression(ILCode.Stloc, tmpVar, expr));
+ foreach(ILVariable storeTo in byteCode.StoreTo.AsEnumerable().Reverse()) {
+ ast.Add(new ILExpression(ILCode.Stloc, storeTo, new ILExpression(ILCode.Ldloc, tmpVar)));
+ }
+ }
+ }
+
+ return ast;
+ }
+ }
+
+ public static class ILAstBuilderExtensionMethods
+ {
+ public static List<T> CutRange<T>(this List<T> list, int start, int count)
+ {
+ List<T> ret = new List<T>(count);
+ for (int i = 0; i < count; i++) {
+ ret.Add(list[start + i]);
+ }
+ list.RemoveRange(start, count);
+ return ret;
+ }
+
+ public static T[] Union<T>(this T[] a, T b)
+ {
+ if (a.Length == 0)
+ return new[] { b };
+ if (Array.IndexOf(a, b) >= 0)
+ return a;
+ var res = new T[a.Length + 1];
+ Array.Copy(a, 0, res, 0, a.Length);
+ res[res.Length - 1] = b;
+ return res;
+ }
+
+ public static T[] Union<T>(this T[] a, T[] b)
+ {
+ if (a == b)
+ return a;
+ if (a.Length == 0)
+ return b;
+ if (b.Length == 0)
+ return a;
+ if (a.Length == 1) {
+ if (b.Length == 1)
+ return a[0].Equals(b[0]) ? a : new[] { a[0], b[0] };
+ return b.Union(a[0]);
+ }
+ if (b.Length == 1)
+ return a.Union(b[0]);
+ return Enumerable.Union(a, b).ToArray();
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs
new file mode 100644
index 00000000..876718bc
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs
@@ -0,0 +1,988 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using ICSharpCode.Decompiler.FlowAnalysis;
+using ICSharpCode.NRefactory.Utils;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using Mono.CSharp;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ public enum ILAstOptimizationStep
+ {
+ RemoveRedundantCode,
+ ReduceBranchInstructionSet,
+ InlineVariables,
+ CopyPropagation,
+ YieldReturn,
+ AsyncAwait,
+ PropertyAccessInstructions,
+ SplitToMovableBlocks,
+ TypeInference,
+ HandlePointerArithmetic,
+ SimplifyShortCircuit,
+ SimplifyTernaryOperator,
+ SimplifyNullCoalescing,
+ JoinBasicBlocks,
+ SimplifyLogicNot,
+ SimplifyShiftOperators,
+ TypeConversionSimplifications,
+ SimplifyLdObjAndStObj,
+ SimplifyCustomShortCircuit,
+ SimplifyLiftedOperators,
+ TransformArrayInitializers,
+ TransformMultidimensionalArrayInitializers,
+ TransformObjectInitializers,
+ MakeAssignmentExpression,
+ IntroducePostIncrement,
+ InlineExpressionTreeParameterDeclarations,
+ InlineVariables2,
+ FindLoops,
+ FindConditions,
+ FlattenNestedMovableBlocks,
+ RemoveEndFinally,
+ RemoveRedundantCode2,
+ GotoRemoval,
+ DuplicateReturns,
+ GotoRemoval2,
+ ReduceIfNesting,
+ InlineVariables3,
+ CachedDelegateInitialization,
+ IntroduceFixedStatements,
+ RecombineVariables,
+ TypeInference2,
+ RemoveRedundantCode3,
+ None
+ }
+
+ public partial class ILAstOptimizer
+ {
+ int nextLabelIndex = 0;
+
+ DecompilerContext context;
+ TypeSystem typeSystem;
+ ILBlock method;
+
+ public void Optimize(DecompilerContext context, ILBlock method, ILAstOptimizationStep abortBeforeStep = ILAstOptimizationStep.None)
+ {
+ this.context = context;
+ typeSystem = context.CurrentMethod.Module.TypeSystem;
+ this.method = method;
+
+ if (abortBeforeStep == ILAstOptimizationStep.RemoveRedundantCode) return;
+ RemoveRedundantCode(method);
+
+ if (abortBeforeStep == ILAstOptimizationStep.ReduceBranchInstructionSet) return;
+ foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) {
+ ReduceBranchInstructionSet(block);
+ }
+ // ReduceBranchInstructionSet runs before inlining because the non-aggressive inlining heuristic
+ // looks at which type of instruction consumes the inlined variable.
+
+ if (abortBeforeStep == ILAstOptimizationStep.InlineVariables) return;
+ // Works better after simple goto removal because of the following debug pattern: stloc X; br Next; Next:; ldloc X
+ ILInlining inlining1 = new ILInlining(method);
+ inlining1.InlineAllVariables();
+
+ if (abortBeforeStep == ILAstOptimizationStep.CopyPropagation) return;
+ inlining1.CopyPropagation();
+
+ if (abortBeforeStep == ILAstOptimizationStep.YieldReturn) return;
+ YieldReturnDecompiler.Run(context, method);
+ AsyncDecompiler.RunStep1(context, method);
+
+ if (abortBeforeStep == ILAstOptimizationStep.AsyncAwait) return;
+ AsyncDecompiler.RunStep2(context, method);
+
+ if (abortBeforeStep == ILAstOptimizationStep.PropertyAccessInstructions) return;
+ IntroducePropertyAccessInstructions(method);
+
+ if (abortBeforeStep == ILAstOptimizationStep.SplitToMovableBlocks) return;
+ foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) {
+ SplitToBasicBlocks(block);
+ }
+
+ if (abortBeforeStep == ILAstOptimizationStep.TypeInference) return;
+ // Types are needed for the ternary operator optimization
+ TypeAnalysis.Run(context, method);
+
+ if (abortBeforeStep == ILAstOptimizationStep.HandlePointerArithmetic) return;
+ HandlePointerArithmetic(method);
+
+ foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) {
+ bool modified;
+ do {
+ modified = false;
+
+ if (abortBeforeStep == ILAstOptimizationStep.SimplifyShortCircuit) return;
+ modified |= block.RunOptimization(new SimpleControlFlow(context, method).SimplifyShortCircuit);
+
+ if (abortBeforeStep == ILAstOptimizationStep.SimplifyTernaryOperator) return;
+ modified |= block.RunOptimization(new SimpleControlFlow(context, method).SimplifyTernaryOperator);
+
+ if (abortBeforeStep == ILAstOptimizationStep.SimplifyNullCoalescing) return;
+ modified |= block.RunOptimization(new SimpleControlFlow(context, method).SimplifyNullCoalescing);
+
+ if (abortBeforeStep == ILAstOptimizationStep.JoinBasicBlocks) return;
+ modified |= block.RunOptimization(new SimpleControlFlow(context, method).JoinBasicBlocks);
+
+ if (abortBeforeStep == ILAstOptimizationStep.SimplifyLogicNot) return;
+ modified |= block.RunOptimization(SimplifyLogicNot);
+
+ if (abortBeforeStep == ILAstOptimizationStep.SimplifyShiftOperators) return;
+ modified |= block.RunOptimization(SimplifyShiftOperators);
+
+ if (abortBeforeStep == ILAstOptimizationStep.TypeConversionSimplifications) return;
+ modified |= block.RunOptimization(TypeConversionSimplifications);
+
+ if (abortBeforeStep == ILAstOptimizationStep.SimplifyLdObjAndStObj) return;
+ modified |= block.RunOptimization(SimplifyLdObjAndStObj);
+
+ if (abortBeforeStep == ILAstOptimizationStep.SimplifyCustomShortCircuit) return;
+ modified |= block.RunOptimization(new SimpleControlFlow(context, method).SimplifyCustomShortCircuit);
+
+ if (abortBeforeStep == ILAstOptimizationStep.SimplifyLiftedOperators) return;
+ modified |= block.RunOptimization(SimplifyLiftedOperators);
+
+ if (abortBeforeStep == ILAstOptimizationStep.TransformArrayInitializers) return;
+ modified |= block.RunOptimization(TransformArrayInitializers);
+
+ if (abortBeforeStep == ILAstOptimizationStep.TransformMultidimensionalArrayInitializers) return;
+ modified |= block.RunOptimization(TransformMultidimensionalArrayInitializers);
+
+ if (abortBeforeStep == ILAstOptimizationStep.TransformObjectInitializers) return;
+ modified |= block.RunOptimization(TransformObjectInitializers);
+
+ if (abortBeforeStep == ILAstOptimizationStep.MakeAssignmentExpression) return;
+ if (context.Settings.MakeAssignmentExpressions) {
+ modified |= block.RunOptimization(MakeAssignmentExpression);
+ }
+ modified |= block.RunOptimization(MakeCompoundAssignments);
+
+ if (abortBeforeStep == ILAstOptimizationStep.IntroducePostIncrement) return;
+ if (context.Settings.IntroduceIncrementAndDecrement) {
+ modified |= block.RunOptimization(IntroducePostIncrement);
+ }
+
+ if (abortBeforeStep == ILAstOptimizationStep.InlineExpressionTreeParameterDeclarations) return;
+ if (context.Settings.ExpressionTrees) {
+ modified |= block.RunOptimization(InlineExpressionTreeParameterDeclarations);
+ }
+
+ if (abortBeforeStep == ILAstOptimizationStep.InlineVariables2) return;
+ modified |= new ILInlining(method).InlineAllInBlock(block);
+ new ILInlining(method).CopyPropagation();
+
+ } while(modified);
+ }
+
+ if (abortBeforeStep == ILAstOptimizationStep.FindLoops) return;
+ foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) {
+ new LoopsAndConditions(context).FindLoops(block);
+ }
+
+ if (abortBeforeStep == ILAstOptimizationStep.FindConditions) return;
+ foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) {
+ new LoopsAndConditions(context).FindConditions(block);
+ }
+
+ if (abortBeforeStep == ILAstOptimizationStep.FlattenNestedMovableBlocks) return;
+ FlattenBasicBlocks(method);
+
+ if (abortBeforeStep == ILAstOptimizationStep.RemoveEndFinally) return;
+ RemoveEndFinally(method);
+
+ if (abortBeforeStep == ILAstOptimizationStep.RemoveRedundantCode2) return;
+ RemoveRedundantCode(method);
+
+ if (abortBeforeStep == ILAstOptimizationStep.GotoRemoval) return;
+ new GotoRemoval().RemoveGotos(method);
+
+ if (abortBeforeStep == ILAstOptimizationStep.DuplicateReturns) return;
+ DuplicateReturnStatements(method);
+
+ if (abortBeforeStep == ILAstOptimizationStep.GotoRemoval2) return;
+ new GotoRemoval().RemoveGotos(method);
+
+ if (abortBeforeStep == ILAstOptimizationStep.ReduceIfNesting) return;
+ ReduceIfNesting(method);
+
+ if (abortBeforeStep == ILAstOptimizationStep.InlineVariables3) return;
+ // The 2nd inlining pass is necessary because DuplicateReturns and the introduction of ternary operators
+ // open up additional inlining possibilities.
+ new ILInlining(method).InlineAllVariables();
+
+ if (abortBeforeStep == ILAstOptimizationStep.CachedDelegateInitialization) return;
+ if (context.Settings.AnonymousMethods) {
+ foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) {
+ for (int i = 0; i < block.Body.Count; i++) {
+ // TODO: Move before loops
+ CachedDelegateInitializationWithField(block, ref i);
+ CachedDelegateInitializationWithLocal(block, ref i);
+ }
+ }
+ }
+
+ if (abortBeforeStep == ILAstOptimizationStep.IntroduceFixedStatements) return;
+ // we need post-order traversal, not pre-order, for "fixed" to work correctly
+ foreach (ILBlock block in TreeTraversal.PostOrder<ILNode>(method, n => n.GetChildren()).OfType<ILBlock>()) {
+ for (int i = block.Body.Count - 1; i >= 0; i--) {
+ // TODO: Move before loops
+ if (i < block.Body.Count)
+ IntroduceFixedStatements(block.Body, i);
+ }
+ }
+
+ if (abortBeforeStep == ILAstOptimizationStep.RecombineVariables) return;
+ RecombineVariables(method);
+
+ if (abortBeforeStep == ILAstOptimizationStep.TypeInference2) return;
+ TypeAnalysis.Reset(method);
+ TypeAnalysis.Run(context, method);
+
+ if (abortBeforeStep == ILAstOptimizationStep.RemoveRedundantCode3) return;
+ GotoRemoval.RemoveRedundantCode(method);
+
+ // ReportUnassignedILRanges(method);
+ }
+
+ /// <summary>
+ /// Removes redundatant Br, Nop, Dup, Pop
+ /// Ignore arguments of 'leave'
+ /// </summary>
+ /// <param name="method"></param>
+ internal static void RemoveRedundantCode(ILBlock method)
+ {
+ Dictionary<ILLabel, int> labelRefCount = new Dictionary<ILLabel, int>();
+ foreach (ILLabel target in method.GetSelfAndChildrenRecursive<ILExpression>(e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())) {
+ labelRefCount[target] = labelRefCount.GetOrDefault(target) + 1;
+ }
+
+ foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) {
+ List<ILNode> body = block.Body;
+ List<ILNode> newBody = new List<ILNode>(body.Count);
+ for (int i = 0; i < body.Count; i++) {
+ ILLabel target;
+ ILExpression popExpr;
+ if (body[i].Match(ILCode.Br, out target) && i+1 < body.Count && body[i+1] == target) {
+ // Ignore the branch
+ if (labelRefCount[target] == 1)
+ i++; // Ignore the label as well
+ } else if (body[i].Match(ILCode.Nop)){
+ // Ignore nop
+ } else if (body[i].Match(ILCode.Pop, out popExpr)) {
+ ILVariable v;
+ if (!popExpr.Match(ILCode.Ldloc, out v))
+ throw new Exception("Pop should have just ldloc at this stage");
+ // Best effort to move the ILRange to previous statement
+ ILVariable prevVar;
+ ILExpression prevExpr;
+ if (i - 1 >= 0 && body[i - 1].Match(ILCode.Stloc, out prevVar, out prevExpr) && prevVar == v)
+ prevExpr.ILRanges.AddRange(((ILExpression)body[i]).ILRanges);
+ // Ignore pop
+ } else {
+ ILLabel label = body[i] as ILLabel;
+ if (label != null) {
+ if (labelRefCount.GetOrDefault(label) > 0)
+ newBody.Add(label);
+ } else {
+ newBody.Add(body[i]);
+ }
+ }
+ }
+ block.Body = newBody;
+ }
+
+ // Ignore arguments of 'leave'
+ foreach (ILExpression expr in method.GetSelfAndChildrenRecursive<ILExpression>(e => e.Code == ILCode.Leave)) {
+ if (expr.Arguments.Any(arg => !arg.Match(ILCode.Ldloc)))
+ throw new Exception("Leave should have just ldloc at this stage");
+ expr.Arguments.Clear();
+ }
+
+ // 'dup' removal
+ foreach (ILExpression expr in method.GetSelfAndChildrenRecursive<ILExpression>()) {
+ for (int i = 0; i < expr.Arguments.Count; i++) {
+ ILExpression child;
+ if (expr.Arguments[i].Match(ILCode.Dup, out child)) {
+ child.ILRanges.AddRange(expr.Arguments[i].ILRanges);
+ expr.Arguments[i] = child;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Reduces the branch codes to just br and brtrue.
+ /// Moves ILRanges to the branch argument
+ /// </summary>
+ void ReduceBranchInstructionSet(ILBlock block)
+ {
+ for (int i = 0; i < block.Body.Count; i++) {
+ ILExpression expr = block.Body[i] as ILExpression;
+ if (expr != null && expr.Prefixes == null) {
+ ILCode op;
+ switch(expr.Code) {
+ case ILCode.Switch:
+ case ILCode.Brtrue:
+ expr.Arguments.Single().ILRanges.AddRange(expr.ILRanges);
+ expr.ILRanges.Clear();
+ continue;
+ case ILCode.__Brfalse: op = ILCode.LogicNot; break;
+ case ILCode.__Beq: op = ILCode.Ceq; break;
+ case ILCode.__Bne_Un: op = ILCode.Cne; break;
+ case ILCode.__Bgt: op = ILCode.Cgt; break;
+ case ILCode.__Bgt_Un: op = ILCode.Cgt_Un; break;
+ case ILCode.__Ble: op = ILCode.Cle; break;
+ case ILCode.__Ble_Un: op = ILCode.Cle_Un; break;
+ case ILCode.__Blt: op = ILCode.Clt; break;
+ case ILCode.__Blt_Un: op = ILCode.Clt_Un; break;
+ case ILCode.__Bge: op = ILCode.Cge; break;
+ case ILCode.__Bge_Un: op = ILCode.Cge_Un; break;
+ default:
+ continue;
+ }
+ var newExpr = new ILExpression(op, null, expr.Arguments);
+ block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, newExpr);
+ newExpr.ILRanges = expr.ILRanges;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Converts call and callvirt instructions that read/write properties into CallGetter/CallSetter instructions.
+ ///
+ /// CallGetter/CallSetter is used to allow the ILAst to represent "while ((SomeProperty = value) != null)".
+ ///
+ /// Also simplifies 'newobj(SomeDelegate, target, ldvirtftn(F, target))' to 'newobj(SomeDelegate, target, ldvirtftn(F))'
+ /// </summary>
+ void IntroducePropertyAccessInstructions(ILNode node)
+ {
+ ILExpression parentExpr = node as ILExpression;
+ if (parentExpr != null) {
+ for (int i = 0; i < parentExpr.Arguments.Count; i++) {
+ ILExpression expr = parentExpr.Arguments[i];
+ IntroducePropertyAccessInstructions(expr);
+ IntroducePropertyAccessInstructions(expr, parentExpr, i);
+ }
+ } else {
+ foreach (ILNode child in node.GetChildren()) {
+ IntroducePropertyAccessInstructions(child);
+ ILExpression expr = child as ILExpression;
+ if (expr != null) {
+ IntroducePropertyAccessInstructions(expr, null, -1);
+ }
+ }
+ }
+ }
+
+ void IntroducePropertyAccessInstructions(ILExpression expr, ILExpression parentExpr, int posInParent)
+ {
+ if (expr.Code == ILCode.Call || expr.Code == ILCode.Callvirt) {
+ MethodReference cecilMethod = (MethodReference)expr.Operand;
+ if (cecilMethod.DeclaringType is ArrayType) {
+ switch (cecilMethod.Name) {
+ case "Get":
+ expr.Code = ILCode.CallGetter;
+ break;
+ case "Set":
+ expr.Code = ILCode.CallSetter;
+ break;
+ case "Address":
+ ByReferenceType brt = cecilMethod.ReturnType as ByReferenceType;
+ if (brt != null) {
+ MethodReference getMethod = new MethodReference("Get", brt.ElementType, cecilMethod.DeclaringType);
+ foreach (var p in cecilMethod.Parameters)
+ getMethod.Parameters.Add(p);
+ getMethod.HasThis = cecilMethod.HasThis;
+ expr.Operand = getMethod;
+ }
+ expr.Code = ILCode.CallGetter;
+ if (parentExpr != null) {
+ parentExpr.Arguments[posInParent] = new ILExpression(ILCode.AddressOf, null, expr);
+ }
+ break;
+ }
+ } else {
+ MethodDefinition cecilMethodDef = cecilMethod.Resolve();
+ if (cecilMethodDef != null) {
+ if (cecilMethodDef.IsGetter)
+ expr.Code = (expr.Code == ILCode.Call) ? ILCode.CallGetter : ILCode.CallvirtGetter;
+ else if (cecilMethodDef.IsSetter)
+ expr.Code = (expr.Code == ILCode.Call) ? ILCode.CallSetter : ILCode.CallvirtSetter;
+ }
+ }
+ } else if (expr.Code == ILCode.Newobj && expr.Arguments.Count == 2) {
+ // Might be 'newobj(SomeDelegate, target, ldvirtftn(F, target))'.
+ ILVariable target;
+ if (expr.Arguments[0].Match(ILCode.Ldloc, out target)
+ && expr.Arguments[1].Code == ILCode.Ldvirtftn
+ && expr.Arguments[1].Arguments.Count == 1
+ && expr.Arguments[1].Arguments[0].MatchLdloc(target))
+ {
+ // Remove the 'target' argument from the ldvirtftn instruction.
+ // It's not needed in the translation to C#, and needs to be eliminated so that the target expression
+ // can be inlined.
+ expr.Arguments[1].Arguments.Clear();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Group input into a set of blocks that can be later arbitraliby schufled.
+ /// The method adds necessary branches to make control flow between blocks
+ /// explicit and thus order independent.
+ /// </summary>
+ void SplitToBasicBlocks(ILBlock block)
+ {
+ List<ILNode> basicBlocks = new List<ILNode>();
+
+ ILLabel entryLabel = block.Body.FirstOrDefault() as ILLabel ?? new ILLabel() { Name = "Block_" + (nextLabelIndex++) };
+ ILBasicBlock basicBlock = new ILBasicBlock();
+ basicBlocks.Add(basicBlock);
+ basicBlock.Body.Add(entryLabel);
+ block.EntryGoto = new ILExpression(ILCode.Br, entryLabel);
+
+ if (block.Body.Count > 0) {
+ if (block.Body[0] != entryLabel)
+ basicBlock.Body.Add(block.Body[0]);
+
+ for (int i = 1; i < block.Body.Count; i++) {
+ ILNode lastNode = block.Body[i - 1];
+ ILNode currNode = block.Body[i];
+
+ // Start a new basic block if necessary
+ if (currNode is ILLabel ||
+ currNode is ILTryCatchBlock || // Counts as label
+ lastNode.IsConditionalControlFlow() ||
+ lastNode.IsUnconditionalControlFlow())
+ {
+ // Try to reuse the label
+ ILLabel label = currNode as ILLabel ?? new ILLabel() { Name = "Block_" + (nextLabelIndex++).ToString() };
+
+ // Terminate the last block
+ if (!lastNode.IsUnconditionalControlFlow()) {
+ // Explicit branch from one block to other
+ basicBlock.Body.Add(new ILExpression(ILCode.Br, label));
+ }
+
+ // Start the new block
+ basicBlock = new ILBasicBlock();
+ basicBlocks.Add(basicBlock);
+ basicBlock.Body.Add(label);
+
+ // Add the node to the basic block
+ if (currNode != label)
+ basicBlock.Body.Add(currNode);
+ } else {
+ basicBlock.Body.Add(currNode);
+ }
+ }
+ }
+
+ block.Body = basicBlocks;
+ return;
+ }
+
+ void DuplicateReturnStatements(ILBlock method)
+ {
+ Dictionary<ILLabel, ILNode> nextSibling = new Dictionary<ILLabel, ILNode>();
+
+ // Build navigation data
+ foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) {
+ for (int i = 0; i < block.Body.Count - 1; i++) {
+ ILLabel curr = block.Body[i] as ILLabel;
+ if (curr != null) {
+ nextSibling[curr] = block.Body[i + 1];
+ }
+ }
+ }
+
+ // Duplicate returns
+ foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) {
+ for (int i = 0; i < block.Body.Count; i++) {
+ ILLabel targetLabel;
+ if (block.Body[i].Match(ILCode.Br, out targetLabel) || block.Body[i].Match(ILCode.Leave, out targetLabel)) {
+ // Skip extra labels
+ while(nextSibling.ContainsKey(targetLabel) && nextSibling[targetLabel] is ILLabel) {
+ targetLabel = (ILLabel)nextSibling[targetLabel];
+ }
+
+ // Inline return statement
+ ILNode target;
+ List<ILExpression> retArgs;
+ if (nextSibling.TryGetValue(targetLabel, out target)) {
+ if (target.Match(ILCode.Ret, out retArgs)) {
+ ILVariable locVar;
+ object constValue;
+ if (retArgs.Count == 0) {
+ block.Body[i] = new ILExpression(ILCode.Ret, null);
+ } else if (retArgs.Single().Match(ILCode.Ldloc, out locVar)) {
+ block.Body[i] = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldloc, locVar));
+ } else if (retArgs.Single().Match(ILCode.Ldc_I4, out constValue)) {
+ block.Body[i] = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldc_I4, constValue));
+ }
+ }
+ } else {
+ if (method.Body.Count > 0 && method.Body.Last() == targetLabel) {
+ // It exits the main method - so it is same as return;
+ block.Body[i] = new ILExpression(ILCode.Ret, null);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Flattens all nested basic blocks, except the the top level 'node' argument
+ /// </summary>
+ void FlattenBasicBlocks(ILNode node)
+ {
+ ILBlock block = node as ILBlock;
+ if (block != null) {
+ List<ILNode> flatBody = new List<ILNode>();
+ foreach (ILNode child in block.GetChildren()) {
+ FlattenBasicBlocks(child);
+ ILBasicBlock childAsBB = child as ILBasicBlock;
+ if (childAsBB != null) {
+ if (!(childAsBB.Body.FirstOrDefault() is ILLabel))
+ throw new Exception("Basic block has to start with a label. \n" + childAsBB.ToString());
+ if (childAsBB.Body.LastOrDefault() is ILExpression && !childAsBB.Body.LastOrDefault().IsUnconditionalControlFlow())
+ throw new Exception("Basci block has to end with unconditional control flow. \n" + childAsBB.ToString());
+ flatBody.AddRange(childAsBB.GetChildren());
+ } else {
+ flatBody.Add(child);
+ }
+ }
+ block.EntryGoto = null;
+ block.Body = flatBody;
+ } else if (node is ILExpression) {
+ // Optimization - no need to check expressions
+ } else if (node != null) {
+ // Recursively find all ILBlocks
+ foreach(ILNode child in node.GetChildren()) {
+ FlattenBasicBlocks(child);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Replace endfinally with jump to the end of the finally block
+ /// </summary>
+ void RemoveEndFinally(ILBlock method)
+ {
+ // Go thought the list in reverse so that we do the nested blocks first
+ foreach(var tryCatch in method.GetSelfAndChildrenRecursive<ILTryCatchBlock>(tc => tc.FinallyBlock != null).Reverse()) {
+ ILLabel label = new ILLabel() { Name = "EndFinally_" + nextLabelIndex++ };
+ tryCatch.FinallyBlock.Body.Add(label);
+ foreach(var block in tryCatch.FinallyBlock.GetSelfAndChildrenRecursive<ILBlock>()) {
+ for (int i = 0; i < block.Body.Count; i++) {
+ if (block.Body[i].Match(ILCode.Endfinally)) {
+ block.Body[i] = new ILExpression(ILCode.Br, label).WithILRanges(((ILExpression)block.Body[i]).ILRanges);
+ }
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Reduce the nesting of conditions.
+ /// It should be done on flat data that already had most gotos removed
+ /// </summary>
+ void ReduceIfNesting(ILNode node)
+ {
+ ILBlock block = node as ILBlock;
+ if (block != null) {
+ for (int i = 0; i < block.Body.Count; i++) {
+ ILCondition cond = block.Body[i] as ILCondition;
+ if (cond != null) {
+ bool trueExits = cond.TrueBlock.Body.LastOrDefault().IsUnconditionalControlFlow();
+ bool falseExits = cond.FalseBlock.Body.LastOrDefault().IsUnconditionalControlFlow();
+
+ if (trueExits) {
+ // Move the false block after the condition
+ block.Body.InsertRange(i + 1, cond.FalseBlock.GetChildren());
+ cond.FalseBlock = new ILBlock();
+ } else if (falseExits) {
+ // Move the true block after the condition
+ block.Body.InsertRange(i + 1, cond.TrueBlock.GetChildren());
+ cond.TrueBlock = new ILBlock();
+ }
+
+ // Eliminate empty true block
+ if (!cond.TrueBlock.GetChildren().Any() && cond.FalseBlock.GetChildren().Any()) {
+ // Swap bodies
+ ILBlock tmp = cond.TrueBlock;
+ cond.TrueBlock = cond.FalseBlock;
+ cond.FalseBlock = tmp;
+ cond.Condition = new ILExpression(ILCode.LogicNot, null, cond.Condition);
+ }
+ }
+ }
+ }
+
+ // We are changing the number of blocks so we use plain old recursion to get all blocks
+ foreach(ILNode child in node.GetChildren()) {
+ if (child != null && !(child is ILExpression))
+ ReduceIfNesting(child);
+ }
+ }
+
+ void RecombineVariables(ILBlock method)
+ {
+ // Recombine variables that were split when the ILAst was created
+ // This ensures that a single IL variable is a single C# variable (gets assigned only one name)
+ // The DeclareVariables transformation might then split up the C# variable again if it is used indendently in two separate scopes.
+ Dictionary<VariableDefinition, ILVariable> dict = new Dictionary<VariableDefinition, ILVariable>();
+ ReplaceVariables(
+ method,
+ delegate(ILVariable v) {
+ if (v.OriginalVariable == null)
+ return v;
+ ILVariable combinedVariable;
+ if (!dict.TryGetValue(v.OriginalVariable, out combinedVariable)) {
+ dict.Add(v.OriginalVariable, v);
+ combinedVariable = v;
+ }
+ return combinedVariable;
+ });
+ }
+
+ static void HandlePointerArithmetic(ILNode method)
+ {
+ foreach (ILExpression expr in method.GetSelfAndChildrenRecursive<ILExpression>()) {
+ List<ILExpression> args = expr.Arguments;
+ switch (expr.Code) {
+ case ILCode.Localloc:
+ args[0] = DivideBySize(args[0], ((PointerType)expr.InferredType).ElementType);
+ break;
+ case ILCode.Add:
+ case ILCode.Add_Ovf:
+ case ILCode.Add_Ovf_Un:
+ if (expr.InferredType is PointerType) {
+ if (args[0].ExpectedType is PointerType)
+ args[1] = DivideBySize(args[1], ((PointerType)expr.InferredType).ElementType);
+ else if (args[1].ExpectedType is PointerType)
+ args[0] = DivideBySize(args[0], ((PointerType)expr.InferredType).ElementType);
+ }
+ break;
+ case ILCode.Sub:
+ case ILCode.Sub_Ovf:
+ case ILCode.Sub_Ovf_Un:
+ if (expr.InferredType is PointerType) {
+ if (args[0].ExpectedType is PointerType)
+ args[1] = DivideBySize(args[1], ((PointerType)expr.InferredType).ElementType);
+ }
+ break;
+ }
+ }
+ }
+
+ static ILExpression UnwrapIntPtrCast(ILExpression expr)
+ {
+ if (expr.Code != ILCode.Conv_I && expr.Code != ILCode.Conv_U)
+ return expr;
+
+ ILExpression arg = expr.Arguments[0];
+ switch (arg.InferredType.MetadataType) {
+ case MetadataType.Byte:
+ case MetadataType.SByte:
+ case MetadataType.UInt16:
+ case MetadataType.Int16:
+ case MetadataType.UInt32:
+ case MetadataType.Int32:
+ case MetadataType.UInt64:
+ case MetadataType.Int64:
+ return arg;
+ }
+
+ return expr;
+ }
+
+ static ILExpression DivideBySize(ILExpression expr, TypeReference type)
+ {
+ expr = UnwrapIntPtrCast(expr);
+
+ ILExpression sizeOfExpression;
+ switch (TypeAnalysis.GetInformationAmount(type)) {
+ case 1:
+ case 8:
+ sizeOfExpression = new ILExpression(ILCode.Ldc_I4, 1);
+ break;
+ case 16:
+ sizeOfExpression = new ILExpression(ILCode.Ldc_I4, 2);
+ break;
+ case 32:
+ sizeOfExpression = new ILExpression(ILCode.Ldc_I4, 4);
+ break;
+ case 64:
+ sizeOfExpression = new ILExpression(ILCode.Ldc_I4, 8);
+ break;
+ default:
+ sizeOfExpression = new ILExpression(ILCode.Sizeof, type);
+ break;
+ }
+
+ if (expr.Code == ILCode.Mul || expr.Code == ILCode.Mul_Ovf || expr.Code == ILCode.Mul_Ovf_Un) {
+ ILExpression mulArg = expr.Arguments[1];
+ if (mulArg.Code == sizeOfExpression.Code && sizeOfExpression.Operand.Equals(mulArg.Operand))
+ return UnwrapIntPtrCast(expr.Arguments[0]);
+ }
+
+ if (expr.Code == sizeOfExpression.Code) {
+ if (sizeOfExpression.Operand.Equals(expr.Operand))
+ return new ILExpression(ILCode.Ldc_I4, 1);
+
+ if (expr.Code == ILCode.Ldc_I4) {
+ int offsetInBytes = (int)expr.Operand;
+ int elementSize = (int)sizeOfExpression.Operand;
+ int offsetInElements = offsetInBytes / elementSize;
+
+ // ensure integer division
+ if (offsetInElements * elementSize == offsetInBytes) {
+ expr.Operand = offsetInElements;
+ return expr;
+ }
+ }
+ }
+
+ return new ILExpression(ILCode.Div_Un, null, expr, sizeOfExpression);
+ }
+
+ public static void ReplaceVariables(ILNode node, Func<ILVariable, ILVariable> variableMapping)
+ {
+ ILExpression expr = node as ILExpression;
+ if (expr != null) {
+ ILVariable v = expr.Operand as ILVariable;
+ if (v != null)
+ expr.Operand = variableMapping(v);
+ foreach (ILExpression child in expr.Arguments)
+ ReplaceVariables(child, variableMapping);
+ } else {
+ var catchBlock = node as ILTryCatchBlock.CatchBlock;
+ if (catchBlock != null && catchBlock.ExceptionVariable != null) {
+ catchBlock.ExceptionVariable = variableMapping(catchBlock.ExceptionVariable);
+ }
+
+ foreach (ILNode child in node.GetChildren())
+ ReplaceVariables(child, variableMapping);
+ }
+ }
+
+ void ReportUnassignedILRanges(ILBlock method)
+ {
+ var unassigned = ILRange.Invert(method.GetSelfAndChildrenRecursive<ILExpression>().SelectMany(e => e.ILRanges), context.CurrentMethod.Body.CodeSize).ToList();
+ if (unassigned.Count > 0)
+ Debug.WriteLine(string.Format("Unassigned ILRanges for {0}.{1}: {2}", context.CurrentMethod.DeclaringType.Name, context.CurrentMethod.Name, string.Join(", ", unassigned.Select(r => r.ToString()))));
+ }
+ }
+
+ public static class ILAstOptimizerExtensionMethods
+ {
+ /// <summary>
+ /// Perform one pass of a given optimization on this block.
+ /// This block must consist of only basicblocks.
+ /// </summary>
+ public static bool RunOptimization(this ILBlock block, Func<List<ILNode>, ILBasicBlock, int, bool> optimization)
+ {
+ bool modified = false;
+ List<ILNode> body = block.Body;
+ for (int i = body.Count - 1; i >= 0; i--) {
+ if (i < body.Count && optimization(body, (ILBasicBlock)body[i], i)) {
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+ public static bool RunOptimization(this ILBlock block, Func<List<ILNode>, ILExpression, int, bool> optimization)
+ {
+ bool modified = false;
+ foreach (ILBasicBlock bb in block.Body) {
+ for (int i = bb.Body.Count - 1; i >= 0; i--) {
+ ILExpression expr = bb.Body.ElementAtOrDefault(i) as ILExpression;
+ if (expr != null && optimization(bb.Body, expr, i)) {
+ modified = true;
+ }
+ }
+ }
+ return modified;
+ }
+
+ public static bool IsConditionalControlFlow(this ILNode node)
+ {
+ ILExpression expr = node as ILExpression;
+ return expr != null && expr.Code.IsConditionalControlFlow();
+ }
+
+ public static bool IsUnconditionalControlFlow(this ILNode node)
+ {
+ ILExpression expr = node as ILExpression;
+ return expr != null && expr.Code.IsUnconditionalControlFlow();
+ }
+
+ /// <summary>
+ /// The expression has no effect on the program and can be removed
+ /// if its return value is not needed.
+ /// </summary>
+ public static bool HasNoSideEffects(this ILExpression expr)
+ {
+ // Remember that if expression can throw an exception, it is a side effect
+
+ switch(expr.Code) {
+ case ILCode.Ldloc:
+ case ILCode.Ldloca:
+ case ILCode.Ldstr:
+ case ILCode.Ldnull:
+ case ILCode.Ldc_I4:
+ case ILCode.Ldc_I8:
+ case ILCode.Ldc_R4:
+ case ILCode.Ldc_R8:
+ case ILCode.Ldc_Decimal:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static bool IsStoreToArray(this ILCode code)
+ {
+ switch (code) {
+ case ILCode.Stelem_Any:
+ case ILCode.Stelem_I:
+ case ILCode.Stelem_I1:
+ case ILCode.Stelem_I2:
+ case ILCode.Stelem_I4:
+ case ILCode.Stelem_I8:
+ case ILCode.Stelem_R4:
+ case ILCode.Stelem_R8:
+ case ILCode.Stelem_Ref:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static bool IsLoadFromArray(this ILCode code)
+ {
+ switch (code) {
+ case ILCode.Ldelem_Any:
+ case ILCode.Ldelem_I:
+ case ILCode.Ldelem_I1:
+ case ILCode.Ldelem_I2:
+ case ILCode.Ldelem_I4:
+ case ILCode.Ldelem_I8:
+ case ILCode.Ldelem_U1:
+ case ILCode.Ldelem_U2:
+ case ILCode.Ldelem_U4:
+ case ILCode.Ldelem_R4:
+ case ILCode.Ldelem_R8:
+ case ILCode.Ldelem_Ref:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Can the expression be used as a statement in C#?
+ /// </summary>
+ public static bool CanBeExpressionStatement(this ILExpression expr)
+ {
+ switch(expr.Code) {
+ case ILCode.Call:
+ case ILCode.Callvirt:
+ // property getters can't be expression statements, but all other method calls can be
+ MethodReference mr = (MethodReference)expr.Operand;
+ return !mr.Name.StartsWith("get_", StringComparison.Ordinal);
+ case ILCode.CallSetter:
+ case ILCode.CallvirtSetter:
+ case ILCode.Newobj:
+ case ILCode.Newarr:
+ case ILCode.Stloc:
+ case ILCode.Stobj:
+ case ILCode.Stsfld:
+ case ILCode.Stfld:
+ case ILCode.Stind_Ref:
+ case ILCode.Stelem_Any:
+ case ILCode.Stelem_I:
+ case ILCode.Stelem_I1:
+ case ILCode.Stelem_I2:
+ case ILCode.Stelem_I4:
+ case ILCode.Stelem_I8:
+ case ILCode.Stelem_R4:
+ case ILCode.Stelem_R8:
+ case ILCode.Stelem_Ref:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static ILExpression WithILRanges(this ILExpression expr, IEnumerable<ILRange> ilranges)
+ {
+ expr.ILRanges.AddRange(ilranges);
+ return expr;
+ }
+
+ public static void RemoveTail(this List<ILNode> body, params ILCode[] codes)
+ {
+ for (int i = 0; i < codes.Length; i++) {
+ if (((ILExpression)body[body.Count - codes.Length + i]).Code != codes[i])
+ throw new Exception("Tailing code does not match expected.");
+ }
+ body.RemoveRange(body.Count - codes.Length, codes.Length);
+ }
+
+ public static V GetOrDefault<K,V>(this Dictionary<K, V> dict, K key)
+ {
+ V ret;
+ dict.TryGetValue(key, out ret);
+ return ret;
+ }
+
+ public static void RemoveOrThrow<T>(this ICollection<T> collection, T item)
+ {
+ if (!collection.Remove(item))
+ throw new Exception("The item was not found in the collection");
+ }
+
+ public static void RemoveOrThrow<K,V>(this Dictionary<K,V> collection, K key)
+ {
+ if (!collection.Remove(key))
+ throw new Exception("The key was not found in the dictionary");
+ }
+
+ public static bool ContainsReferenceTo(this ILExpression expr, ILVariable v)
+ {
+ if (expr.Operand == v)
+ return true;
+ foreach (var arg in expr.Arguments) {
+ if (ContainsReferenceTo(arg, v))
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs
new file mode 100644
index 00000000..aab64e6e
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs
@@ -0,0 +1,601 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using ICSharpCode.Decompiler;
+using ICSharpCode.Decompiler.Disassembler;
+using ICSharpCode.NRefactory.Utils;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using Mono.CSharp;
+using Cecil = Mono.Cecil;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ public abstract class ILNode
+ {
+ public IEnumerable<T> GetSelfAndChildrenRecursive<T>(Func<T, bool> predicate = null) where T: ILNode
+ {
+ List<T> result = new List<T>(16);
+ AccumulateSelfAndChildrenRecursive(result, predicate);
+ return result;
+ }
+
+ void AccumulateSelfAndChildrenRecursive<T>(List<T> list, Func<T, bool> predicate) where T:ILNode
+ {
+ // Note: RemoveEndFinally depends on self coming before children
+ T thisAsT = this as T;
+ if (thisAsT != null && (predicate == null || predicate(thisAsT)))
+ list.Add(thisAsT);
+ foreach (ILNode node in GetChildren()) {
+ if (node != null)
+ node.AccumulateSelfAndChildrenRecursive(list, predicate);
+ }
+ }
+
+ public virtual IEnumerable<ILNode> GetChildren()
+ {
+ yield break;
+ }
+
+ public override string ToString()
+ {
+ StringWriter w = new StringWriter();
+ WriteTo(new PlainTextOutput(w));
+ return w.ToString().Replace("\r\n", "; ");
+ }
+
+ public abstract void WriteTo(ITextOutput output);
+ }
+
+ public class ILBlock: ILNode
+ {
+ public ILExpression EntryGoto;
+
+ public List<ILNode> Body;
+
+ public ILBlock(params ILNode[] body)
+ {
+ Body = new List<ILNode>(body);
+ }
+
+ public ILBlock(List<ILNode> body)
+ {
+ Body = body;
+ }
+
+ public override IEnumerable<ILNode> GetChildren()
+ {
+ if (EntryGoto != null)
+ yield return EntryGoto;
+ foreach(ILNode child in Body) {
+ yield return child;
+ }
+ }
+
+ public override void WriteTo(ITextOutput output)
+ {
+ foreach(ILNode child in GetChildren()) {
+ child.WriteTo(output);
+ output.WriteLine();
+ }
+ }
+ }
+
+ public class ILBasicBlock: ILNode
+ {
+ /// <remarks> Body has to start with a label and end with unconditional control flow </remarks>
+ public List<ILNode> Body = new List<ILNode>();
+
+ public override IEnumerable<ILNode> GetChildren()
+ {
+ return Body;
+ }
+
+ public override void WriteTo(ITextOutput output)
+ {
+ foreach(ILNode child in GetChildren()) {
+ child.WriteTo(output);
+ output.WriteLine();
+ }
+ }
+ }
+
+ public class ILLabel: ILNode
+ {
+ public string Name;
+
+ public override void WriteTo(ITextOutput output)
+ {
+ output.WriteDefinition(Name + ":", this);
+ }
+ }
+
+ public class ILTryCatchBlock: ILNode
+ {
+ public class CatchBlock: ILBlock
+ {
+ public TypeReference ExceptionType;
+ public ILVariable ExceptionVariable;
+
+ public override void WriteTo(ITextOutput output)
+ {
+ output.Write("catch ");
+ output.WriteReference(ExceptionType.FullName, ExceptionType);
+ if (ExceptionVariable != null) {
+ output.Write(' ');
+ output.Write(ExceptionVariable.Name);
+ }
+ output.WriteLine(" {");
+ output.Indent();
+ base.WriteTo(output);
+ output.Unindent();
+ output.WriteLine("}");
+ }
+ }
+
+ public ILBlock TryBlock;
+ public List<CatchBlock> CatchBlocks;
+ public ILBlock FinallyBlock;
+ public ILBlock FaultBlock;
+
+ public override IEnumerable<ILNode> GetChildren()
+ {
+ if (TryBlock != null)
+ yield return TryBlock;
+ foreach (var catchBlock in CatchBlocks) {
+ yield return catchBlock;
+ }
+ if (FaultBlock != null)
+ yield return FaultBlock;
+ if (FinallyBlock != null)
+ yield return FinallyBlock;
+ }
+
+ public override void WriteTo(ITextOutput output)
+ {
+ output.WriteLine(".try {");
+ output.Indent();
+ TryBlock.WriteTo(output);
+ output.Unindent();
+ output.WriteLine("}");
+ foreach (CatchBlock block in CatchBlocks) {
+ block.WriteTo(output);
+ }
+ if (FaultBlock != null) {
+ output.WriteLine("fault {");
+ output.Indent();
+ FaultBlock.WriteTo(output);
+ output.Unindent();
+ output.WriteLine("}");
+ }
+ if (FinallyBlock != null) {
+ output.WriteLine("finally {");
+ output.Indent();
+ FinallyBlock.WriteTo(output);
+ output.Unindent();
+ output.WriteLine("}");
+ }
+ }
+ }
+
+ public class ILVariable
+ {
+ public string Name;
+ public bool IsGenerated;
+ public TypeReference Type;
+ public VariableDefinition OriginalVariable;
+ public ParameterDefinition OriginalParameter;
+
+ public bool IsPinned {
+ get { return OriginalVariable != null && OriginalVariable.IsPinned; }
+ }
+
+ public bool IsParameter {
+ get { return OriginalParameter != null; }
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+ }
+
+ public struct ILRange
+ {
+ public readonly int From;
+ public readonly int To; // Exlusive
+
+ public ILRange(int @from, int to)
+ {
+ From = @from;
+ To = to;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("{0:X2}-{1:X2}", From, To);
+ }
+
+ public static List<ILRange> OrderAndJoin(IEnumerable<ILRange> input)
+ {
+ if (input == null)
+ throw new ArgumentNullException("Input is null!");
+
+ List<ILRange> result = new List<ILRange>();
+ foreach(ILRange curr in input.OrderBy(r => r.From)) {
+ if (result.Count > 0) {
+ // Merge consequtive ranges if possible
+ ILRange last = result[result.Count - 1];
+ if (curr.From <= last.To) {
+ result[result.Count - 1] = new ILRange(last.From, Math.Max(last.To, curr.To));
+ continue;
+ }
+ }
+ result.Add(curr);
+ }
+ return result;
+ }
+
+ public static List<ILRange> Invert(IEnumerable<ILRange> input, int codeSize)
+ {
+ if (input == null)
+ throw new ArgumentNullException("Input is null!");
+
+ if (codeSize <= 0)
+ throw new ArgumentException("Code size must be grater than 0");
+
+ List<ILRange> ordered = OrderAndJoin(input);
+ List<ILRange> result = new List<ILRange>(ordered.Count + 1);
+ if (ordered.Count == 0) {
+ result.Add(new ILRange(0, codeSize));
+ } else {
+ // Gap before the first element
+ if (ordered.First().From != 0)
+ result.Add(new ILRange(0, ordered.First().From));
+
+ // Gaps between elements
+ for (int i = 0; i < ordered.Count - 1; i++)
+ result.Add(new ILRange(ordered[i].To, ordered[i + 1].From));
+
+ // Gap after the last element
+ Debug.Assert(ordered.Last().To <= codeSize);
+ if (ordered.Last().To != codeSize)
+ result.Add(new ILRange(ordered.Last().To, codeSize));
+ }
+ return result;
+ }
+ }
+
+ public class ILExpressionPrefix
+ {
+ public readonly ILCode Code;
+ public readonly object Operand;
+
+ public ILExpressionPrefix(ILCode code, object operand = null)
+ {
+ Code = code;
+ Operand = operand;
+ }
+ }
+
+ public class ILExpression : ILNode
+ {
+ public ILCode Code { get; set; }
+ public object Operand { get; set; }
+ public List<ILExpression> Arguments { get; set; }
+ public ILExpressionPrefix[] Prefixes { get; set; }
+ // Mapping to the original instructions (useful for debugging)
+ public List<ILRange> ILRanges { get; set; }
+
+ public TypeReference ExpectedType { get; set; }
+ public TypeReference InferredType { get; set; }
+
+ public static readonly object AnyOperand = new object();
+
+ public ILExpression(ILCode code, object operand, List<ILExpression> args)
+ {
+ if (operand is ILExpression)
+ throw new ArgumentException("operand");
+
+ Code = code;
+ Operand = operand;
+ Arguments = new List<ILExpression>(args);
+ ILRanges = new List<ILRange>(1);
+ }
+
+ public ILExpression(ILCode code, object operand, params ILExpression[] args)
+ {
+ if (operand is ILExpression)
+ throw new ArgumentException("operand");
+
+ Code = code;
+ Operand = operand;
+ Arguments = new List<ILExpression>(args);
+ ILRanges = new List<ILRange>(1);
+ }
+
+ public void AddPrefix(ILExpressionPrefix prefix)
+ {
+ ILExpressionPrefix[] arr = Prefixes;
+ if (arr == null)
+ arr = new ILExpressionPrefix[1];
+ else
+ Array.Resize(ref arr, arr.Length + 1);
+ arr[arr.Length - 1] = prefix;
+ Prefixes = arr;
+ }
+
+ public ILExpressionPrefix GetPrefix(ILCode code)
+ {
+ var prefixes = Prefixes;
+ if (prefixes != null) {
+ foreach (ILExpressionPrefix p in prefixes) {
+ if (p.Code == code)
+ return p;
+ }
+ }
+ return null;
+ }
+
+ public override IEnumerable<ILNode> GetChildren()
+ {
+ return Arguments;
+ }
+
+ public bool IsBranch()
+ {
+ return Operand is ILLabel || Operand is ILLabel[];
+ }
+
+ public IEnumerable<ILLabel> GetBranchTargets()
+ {
+ if (Operand is ILLabel) {
+ return new ILLabel[] { (ILLabel)Operand };
+ } else if (Operand is ILLabel[]) {
+ return (ILLabel[])Operand;
+ } else {
+ return new ILLabel[] { };
+ }
+ }
+
+ public override void WriteTo(ITextOutput output)
+ {
+ if (Operand is ILVariable && ((ILVariable)Operand).IsGenerated) {
+ if (Code == ILCode.Stloc && InferredType == null) {
+ output.Write(((ILVariable)Operand).Name);
+ output.Write(" = ");
+ Arguments.First().WriteTo(output);
+ return;
+ } else if (Code == ILCode.Ldloc) {
+ output.Write(((ILVariable)Operand).Name);
+ if (InferredType != null) {
+ output.Write(':');
+ InferredType.WriteTo(output, ILNameSyntax.ShortTypeName);
+ if (ExpectedType != null && ExpectedType.FullName != InferredType.FullName) {
+ output.Write("[exp:");
+ ExpectedType.WriteTo(output, ILNameSyntax.ShortTypeName);
+ output.Write(']');
+ }
+ }
+ return;
+ }
+ }
+
+ if (Prefixes != null) {
+ foreach (var prefix in Prefixes) {
+ output.Write(prefix.Code.GetName());
+ output.Write(". ");
+ }
+ }
+
+ output.Write(Code.GetName());
+ if (InferredType != null) {
+ output.Write(':');
+ InferredType.WriteTo(output, ILNameSyntax.ShortTypeName);
+ if (ExpectedType != null && ExpectedType.FullName != InferredType.FullName) {
+ output.Write("[exp:");
+ ExpectedType.WriteTo(output, ILNameSyntax.ShortTypeName);
+ output.Write(']');
+ }
+ } else if (ExpectedType != null) {
+ output.Write("[exp:");
+ ExpectedType.WriteTo(output, ILNameSyntax.ShortTypeName);
+ output.Write(']');
+ }
+ output.Write('(');
+ bool first = true;
+ if (Operand != null) {
+ if (Operand is ILLabel) {
+ output.WriteReference(((ILLabel)Operand).Name, Operand);
+ } else if (Operand is ILLabel[]) {
+ ILLabel[] labels = (ILLabel[])Operand;
+ for (int i = 0; i < labels.Length; i++) {
+ if (i > 0)
+ output.Write(", ");
+ output.WriteReference(labels[i].Name, labels[i]);
+ }
+ } else if (Operand is MethodReference) {
+ MethodReference method = (MethodReference)Operand;
+ if (method.DeclaringType != null) {
+ method.DeclaringType.WriteTo(output, ILNameSyntax.ShortTypeName);
+ output.Write("::");
+ }
+ output.WriteReference(method.Name, method);
+ } else if (Operand is FieldReference) {
+ FieldReference field = (FieldReference)Operand;
+ field.DeclaringType.WriteTo(output, ILNameSyntax.ShortTypeName);
+ output.Write("::");
+ output.WriteReference(field.Name, field);
+ } else {
+ DisassemblerHelpers.WriteOperand(output, Operand);
+ }
+ first = false;
+ }
+ foreach (ILExpression arg in Arguments) {
+ if (!first) output.Write(", ");
+ arg.WriteTo(output);
+ first = false;
+ }
+ output.Write(')');
+ }
+ }
+
+ public class ILWhileLoop : ILNode
+ {
+ public ILExpression Condition;
+ public ILBlock BodyBlock;
+
+ public override IEnumerable<ILNode> GetChildren()
+ {
+ if (Condition != null)
+ yield return Condition;
+ if (BodyBlock != null)
+ yield return BodyBlock;
+ }
+
+ public override void WriteTo(ITextOutput output)
+ {
+ output.WriteLine("");
+ output.Write("loop (");
+ if (Condition != null)
+ Condition.WriteTo(output);
+ output.WriteLine(") {");
+ output.Indent();
+ BodyBlock.WriteTo(output);
+ output.Unindent();
+ output.WriteLine("}");
+ }
+ }
+
+ public class ILCondition : ILNode
+ {
+ public ILExpression Condition;
+ public ILBlock TrueBlock; // Branch was taken
+ public ILBlock FalseBlock; // Fall-though
+
+ public override IEnumerable<ILNode> GetChildren()
+ {
+ if (Condition != null)
+ yield return Condition;
+ if (TrueBlock != null)
+ yield return TrueBlock;
+ if (FalseBlock != null)
+ yield return FalseBlock;
+ }
+
+ public override void WriteTo(ITextOutput output)
+ {
+ output.Write("if (");
+ Condition.WriteTo(output);
+ output.WriteLine(") {");
+ output.Indent();
+ TrueBlock.WriteTo(output);
+ output.Unindent();
+ output.Write("}");
+ if (FalseBlock != null) {
+ output.WriteLine(" else {");
+ output.Indent();
+ FalseBlock.WriteTo(output);
+ output.Unindent();
+ output.WriteLine("}");
+ }
+ }
+ }
+
+ public class ILSwitch: ILNode
+ {
+ public class CaseBlock: ILBlock
+ {
+ public List<int> Values; // null for the default case
+
+ public override void WriteTo(ITextOutput output)
+ {
+ if (Values != null) {
+ foreach (int i in Values) {
+ output.WriteLine("case {0}:", i);
+ }
+ } else {
+ output.WriteLine("default:");
+ }
+ output.Indent();
+ base.WriteTo(output);
+ output.Unindent();
+ }
+ }
+
+ public ILExpression Condition;
+ public List<CaseBlock> CaseBlocks = new List<CaseBlock>();
+
+ public override IEnumerable<ILNode> GetChildren()
+ {
+ if (Condition != null)
+ yield return Condition;
+ foreach (ILBlock caseBlock in CaseBlocks) {
+ yield return caseBlock;
+ }
+ }
+
+ public override void WriteTo(ITextOutput output)
+ {
+ output.Write("switch (");
+ Condition.WriteTo(output);
+ output.WriteLine(") {");
+ output.Indent();
+ foreach (CaseBlock caseBlock in CaseBlocks) {
+ caseBlock.WriteTo(output);
+ }
+ output.Unindent();
+ output.WriteLine("}");
+ }
+ }
+
+ public class ILFixedStatement : ILNode
+ {
+ public List<ILExpression> Initializers = new List<ILExpression>();
+ public ILBlock BodyBlock;
+
+ public override IEnumerable<ILNode> GetChildren()
+ {
+ foreach (ILExpression initializer in Initializers)
+ yield return initializer;
+ if (BodyBlock != null)
+ yield return BodyBlock;
+ }
+
+ public override void WriteTo(ITextOutput output)
+ {
+ output.Write("fixed (");
+ for (int i = 0; i < Initializers.Count; i++) {
+ if (i > 0)
+ output.Write(", ");
+ Initializers[i].WriteTo(output);
+ }
+ output.WriteLine(") {");
+ output.Indent();
+ BodyBlock.WriteTo(output);
+ output.Unindent();
+ output.WriteLine("}");
+ }
+ }
+} \ No newline at end of file
diff --git a/ICSharpCode.Decompiler/ILAst/ILCodes.cs b/ICSharpCode.Decompiler/ILAst/ILCodes.cs
new file mode 100644
index 00000000..5ca063b7
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/ILCodes.cs
@@ -0,0 +1,490 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ public enum ILCode
+ {
+ // For convenience, the start is exactly identical to Mono.Cecil.Cil.Code
+ // Instructions that should not be used are prepended by __
+ Nop,
+ Break,
+ __Ldarg_0,
+ __Ldarg_1,
+ __Ldarg_2,
+ __Ldarg_3,
+ __Ldloc_0,
+ __Ldloc_1,
+ __Ldloc_2,
+ __Ldloc_3,
+ __Stloc_0,
+ __Stloc_1,
+ __Stloc_2,
+ __Stloc_3,
+ __Ldarg_S,
+ __Ldarga_S,
+ __Starg_S,
+ __Ldloc_S,
+ __Ldloca_S,
+ __Stloc_S,
+ Ldnull,
+ __Ldc_I4_M1,
+ __Ldc_I4_0,
+ __Ldc_I4_1,
+ __Ldc_I4_2,
+ __Ldc_I4_3,
+ __Ldc_I4_4,
+ __Ldc_I4_5,
+ __Ldc_I4_6,
+ __Ldc_I4_7,
+ __Ldc_I4_8,
+ __Ldc_I4_S,
+ Ldc_I4,
+ Ldc_I8,
+ Ldc_R4,
+ Ldc_R8,
+ Dup,
+ Pop,
+ Jmp,
+ Call,
+ Calli,
+ Ret,
+ __Br_S,
+ __Brfalse_S,
+ __Brtrue_S,
+ __Beq_S,
+ __Bge_S,
+ __Bgt_S,
+ __Ble_S,
+ __Blt_S,
+ __Bne_Un_S,
+ __Bge_Un_S,
+ __Bgt_Un_S,
+ __Ble_Un_S,
+ __Blt_Un_S,
+ Br,
+ __Brfalse,
+ Brtrue,
+ __Beq,
+ __Bge,
+ __Bgt,
+ __Ble,
+ __Blt,
+ __Bne_Un,
+ __Bge_Un,
+ __Bgt_Un,
+ __Ble_Un,
+ __Blt_Un,
+ Switch,
+ __Ldind_I1,
+ __Ldind_U1,
+ __Ldind_I2,
+ __Ldind_U2,
+ __Ldind_I4,
+ __Ldind_U4,
+ __Ldind_I8,
+ __Ldind_I,
+ __Ldind_R4,
+ __Ldind_R8,
+ Ldind_Ref,
+ Stind_Ref,
+ __Stind_I1,
+ __Stind_I2,
+ __Stind_I4,
+ __Stind_I8,
+ __Stind_R4,
+ __Stind_R8,
+ Add,
+ Sub,
+ Mul,
+ Div,
+ Div_Un,
+ Rem,
+ Rem_Un,
+ And,
+ Or,
+ Xor,
+ Shl,
+ Shr,
+ Shr_Un,
+ Neg,
+ Not,
+ Conv_I1,
+ Conv_I2,
+ Conv_I4,
+ Conv_I8,
+ Conv_R4,
+ Conv_R8,
+ Conv_U4,
+ Conv_U8,
+ Callvirt,
+ Cpobj,
+ Ldobj,
+ Ldstr,
+ Newobj,
+ Castclass,
+ Isinst,
+ Conv_R_Un,
+ Unbox,
+ Throw,
+ Ldfld,
+ Ldflda,
+ Stfld,
+ Ldsfld,
+ Ldsflda,
+ Stsfld,
+ Stobj,
+ Conv_Ovf_I1_Un,
+ Conv_Ovf_I2_Un,
+ Conv_Ovf_I4_Un,
+ Conv_Ovf_I8_Un,
+ Conv_Ovf_U1_Un,
+ Conv_Ovf_U2_Un,
+ Conv_Ovf_U4_Un,
+ Conv_Ovf_U8_Un,
+ Conv_Ovf_I_Un,
+ Conv_Ovf_U_Un,
+ Box,
+ Newarr,
+ Ldlen,
+ Ldelema,
+ Ldelem_I1,
+ Ldelem_U1,
+ Ldelem_I2,
+ Ldelem_U2,
+ Ldelem_I4,
+ Ldelem_U4,
+ Ldelem_I8,
+ Ldelem_I,
+ Ldelem_R4,
+ Ldelem_R8,
+ Ldelem_Ref,
+ Stelem_I,
+ Stelem_I1,
+ Stelem_I2,
+ Stelem_I4,
+ Stelem_I8,
+ Stelem_R4,
+ Stelem_R8,
+ Stelem_Ref,
+ Ldelem_Any,
+ Stelem_Any,
+ Unbox_Any,
+ Conv_Ovf_I1,
+ Conv_Ovf_U1,
+ Conv_Ovf_I2,
+ Conv_Ovf_U2,
+ Conv_Ovf_I4,
+ Conv_Ovf_U4,
+ Conv_Ovf_I8,
+ Conv_Ovf_U8,
+ Refanyval,
+ Ckfinite,
+ Mkrefany,
+ Ldtoken,
+ Conv_U2,
+ Conv_U1,
+ Conv_I,
+ Conv_Ovf_I,
+ Conv_Ovf_U,
+ Add_Ovf,
+ Add_Ovf_Un,
+ Mul_Ovf,
+ Mul_Ovf_Un,
+ Sub_Ovf,
+ Sub_Ovf_Un,
+ Endfinally,
+ Leave,
+ __Leave_S,
+ __Stind_I,
+ Conv_U,
+ Arglist,
+ Ceq,
+ Cgt,
+ Cgt_Un,
+ Clt,
+ Clt_Un,
+ Ldftn,
+ Ldvirtftn,
+ __Ldarg,
+ __Ldarga,
+ __Starg,
+ Ldloc,
+ Ldloca,
+ Stloc,
+ Localloc,
+ Endfilter,
+ Unaligned,
+ Volatile,
+ Tail,
+ Initobj,
+ Constrained,
+ Cpblk,
+ Initblk,
+ No,
+ Rethrow,
+ Sizeof,
+ Refanytype,
+ Readonly,
+
+ // Virtual codes - defined for convenience
+ Cne,
+ Cge,
+ Cge_Un,
+ Cle,
+ Cle_Un,
+ Ldexception, // Operand holds the CatchType for catch handler, null for filter
+ LogicNot,
+ LogicAnd,
+ LogicOr,
+ NullCoalescing,
+ InitArray, // Array Initializer
+
+ /// <summary>
+ /// Defines a barrier between the parent expression and the argument expression that prevents combining them
+ /// </summary>
+ Wrap,
+
+ // new Class { Prop = 1, Collection = { { 2, 3 }, {4, 5} }}
+ // is represented as:
+ // InitObject(newobj Class,
+ // CallSetter(Prop, InitializedObject, 1),
+ // InitCollection(CallGetter(Collection, InitializedObject))),
+ // Call(Add, InitializedObject, 2, 3),
+ // Call(Add, InitializedObject, 4, 5)))
+ InitObject, // Object initializer: first arg is newobj/defaultvalue, remaining args are the initializing statements
+ InitCollection, // Collection initializer: first arg is newobj/defaultvalue, remaining args are the initializing statements
+ InitializedObject, // Refers the the object being initialized (refers to first arg in parent InitObject or InitCollection instruction)
+
+ TernaryOp, // ?:
+ LoopOrSwitchBreak,
+ LoopContinue,
+ Ldc_Decimal,
+ YieldBreak,
+ YieldReturn,
+ /// <summary>
+ /// Represents the 'default(T)' instruction.
+ /// </summary>
+ /// <remarks>Introduced by SimplifyLdObjAndStObj step</remarks>
+ DefaultValue,
+ /// <summary>
+ /// ILExpression with a single child: binary operator.
+ /// This expression means that the binary operator will also assign the new value to its left-hand side.
+ /// 'CompoundAssignment' must not be used for local variables, as inlining (and other) optimizations don't know that it modifies the variable.
+ /// </summary>
+ /// <remarks>Introduced by MakeCompoundAssignments step</remarks>
+ CompoundAssignment,
+ /// <summary>
+ /// Represents the post-increment operator.
+ /// The first argument is the address of the variable to increment (ldloca instruction).
+ /// The second arugment is the amount the variable is incremented by (ldc.i4 instruction)
+ /// </summary>
+ /// <remarks>Introduced by IntroducePostIncrement step</remarks>
+ PostIncrement,
+ PostIncrement_Ovf, // checked variant of PostIncrement
+ PostIncrement_Ovf_Un, // checked variant of PostIncrement, for unsigned integers
+ /// <summary>Calls the getter of a static property (or indexer), or of an instance property on 'base'</summary>
+ CallGetter,
+ /// <summary>Calls the getter of an instance property (or indexer)</summary>
+ CallvirtGetter,
+ /// <summary>Calls the setter of a static property (or indexer), or of an instance property on 'base'</summary>
+ /// <remarks>This allows us to represent "while ((SomeProperty = val) != null) {}"</remarks>
+ CallSetter,
+ /// <summary>Calls the setter of a instance property (or indexer)</summary>
+ CallvirtSetter,
+ /// <summary>Simulates getting the address of the argument instruction.</summary>
+ /// <remarks>
+ /// Used for postincrement for properties, and to represent the Address() method on multi-dimensional arrays.
+ /// Also used when inlining a method call on a value type: "stloc(v, ...); call(M, ldloca(v));" becomes "call(M, AddressOf(...))"
+ /// </remarks>
+ AddressOf,
+ /// <summary>Simulates getting the value of a lifted operator's nullable argument</summary>
+ /// <remarks>
+ /// For example "stloc(v1, ...); stloc(v2, ...); logicand(ceq(call(Nullable`1::GetValueOrDefault, ldloca(v1)), ldloc(v2)), callgetter(Nullable`1::get_HasValue, ldloca(v1)))" becomes "wrap(ceq(ValueOf(...), ...))"
+ /// </remarks>
+ ValueOf,
+ /// <summary>Simulates creating a new nullable value from a value type argument</summary>
+ /// <remarks>
+ /// For example "stloc(v1, ...); stloc(v2, ...); ternaryop(callgetter(Nullable`1::get_HasValue, ldloca(v1)), newobj(Nullable`1::.ctor, add(call(Nullable`1::GetValueOrDefault, ldloca(v1)), ldloc(v2))), defaultvalue(Nullable`1))"
+ /// becomes "NullableOf(add(valueof(...), ...))"
+ /// </remarks>
+ NullableOf,
+ /// <summary>
+ /// Declares parameters that are used in an expression tree.
+ /// The last child of this node is the call constructing the expression tree, all other children are the
+ /// assignments to the ParameterExpression variables.
+ /// </summary>
+ ExpressionTreeParameterDeclarations,
+ /// <summary>
+ /// C# 5 await
+ /// </summary>
+ Await
+ }
+
+ public static class ILCodeUtil
+ {
+ public static string GetName(this ILCode code)
+ {
+ return code.ToString().ToLowerInvariant().TrimStart('_').Replace('_','.');
+ }
+
+ public static bool IsConditionalControlFlow(this ILCode code)
+ {
+ switch(code) {
+ case ILCode.__Brfalse_S:
+ case ILCode.__Brtrue_S:
+ case ILCode.__Beq_S:
+ case ILCode.__Bge_S:
+ case ILCode.__Bgt_S:
+ case ILCode.__Ble_S:
+ case ILCode.__Blt_S:
+ case ILCode.__Bne_Un_S:
+ case ILCode.__Bge_Un_S:
+ case ILCode.__Bgt_Un_S:
+ case ILCode.__Ble_Un_S:
+ case ILCode.__Blt_Un_S:
+ case ILCode.__Brfalse:
+ case ILCode.Brtrue:
+ case ILCode.__Beq:
+ case ILCode.__Bge:
+ case ILCode.__Bgt:
+ case ILCode.__Ble:
+ case ILCode.__Blt:
+ case ILCode.__Bne_Un:
+ case ILCode.__Bge_Un:
+ case ILCode.__Bgt_Un:
+ case ILCode.__Ble_Un:
+ case ILCode.__Blt_Un:
+ case ILCode.Switch:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static bool IsUnconditionalControlFlow(this ILCode code)
+ {
+ switch(code) {
+ case ILCode.Br:
+ case ILCode.__Br_S:
+ case ILCode.Leave:
+ case ILCode.__Leave_S:
+ case ILCode.Ret:
+ case ILCode.Endfilter:
+ case ILCode.Endfinally:
+ case ILCode.Throw:
+ case ILCode.Rethrow:
+ case ILCode.LoopContinue:
+ case ILCode.LoopOrSwitchBreak:
+ case ILCode.YieldBreak:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static void ExpandMacro(ref ILCode code, ref object operand, MethodBody methodBody)
+ {
+ switch (code) {
+ case ILCode.__Ldarg_0: code = ILCode.__Ldarg; operand = methodBody.GetParameter(0); break;
+ case ILCode.__Ldarg_1: code = ILCode.__Ldarg; operand = methodBody.GetParameter(1); break;
+ case ILCode.__Ldarg_2: code = ILCode.__Ldarg; operand = methodBody.GetParameter(2); break;
+ case ILCode.__Ldarg_3: code = ILCode.__Ldarg; operand = methodBody.GetParameter(3); break;
+ case ILCode.__Ldloc_0: code = ILCode.Ldloc; operand = methodBody.Variables[0]; break;
+ case ILCode.__Ldloc_1: code = ILCode.Ldloc; operand = methodBody.Variables[1]; break;
+ case ILCode.__Ldloc_2: code = ILCode.Ldloc; operand = methodBody.Variables[2]; break;
+ case ILCode.__Ldloc_3: code = ILCode.Ldloc; operand = methodBody.Variables[3]; break;
+ case ILCode.__Stloc_0: code = ILCode.Stloc; operand = methodBody.Variables[0]; break;
+ case ILCode.__Stloc_1: code = ILCode.Stloc; operand = methodBody.Variables[1]; break;
+ case ILCode.__Stloc_2: code = ILCode.Stloc; operand = methodBody.Variables[2]; break;
+ case ILCode.__Stloc_3: code = ILCode.Stloc; operand = methodBody.Variables[3]; break;
+ case ILCode.__Ldarg_S: code = ILCode.__Ldarg; break;
+ case ILCode.__Ldarga_S: code = ILCode.__Ldarga; break;
+ case ILCode.__Starg_S: code = ILCode.__Starg; break;
+ case ILCode.__Ldloc_S: code = ILCode.Ldloc; break;
+ case ILCode.__Ldloca_S: code = ILCode.Ldloca; break;
+ case ILCode.__Stloc_S: code = ILCode.Stloc; break;
+ case ILCode.__Ldc_I4_M1: code = ILCode.Ldc_I4; operand = -1; break;
+ case ILCode.__Ldc_I4_0: code = ILCode.Ldc_I4; operand = 0; break;
+ case ILCode.__Ldc_I4_1: code = ILCode.Ldc_I4; operand = 1; break;
+ case ILCode.__Ldc_I4_2: code = ILCode.Ldc_I4; operand = 2; break;
+ case ILCode.__Ldc_I4_3: code = ILCode.Ldc_I4; operand = 3; break;
+ case ILCode.__Ldc_I4_4: code = ILCode.Ldc_I4; operand = 4; break;
+ case ILCode.__Ldc_I4_5: code = ILCode.Ldc_I4; operand = 5; break;
+ case ILCode.__Ldc_I4_6: code = ILCode.Ldc_I4; operand = 6; break;
+ case ILCode.__Ldc_I4_7: code = ILCode.Ldc_I4; operand = 7; break;
+ case ILCode.__Ldc_I4_8: code = ILCode.Ldc_I4; operand = 8; break;
+ case ILCode.__Ldc_I4_S: code = ILCode.Ldc_I4; operand = (int) (sbyte) operand; break;
+ case ILCode.__Br_S: code = ILCode.Br; break;
+ case ILCode.__Brfalse_S: code = ILCode.__Brfalse; break;
+ case ILCode.__Brtrue_S: code = ILCode.Brtrue; break;
+ case ILCode.__Beq_S: code = ILCode.__Beq; break;
+ case ILCode.__Bge_S: code = ILCode.__Bge; break;
+ case ILCode.__Bgt_S: code = ILCode.__Bgt; break;
+ case ILCode.__Ble_S: code = ILCode.__Ble; break;
+ case ILCode.__Blt_S: code = ILCode.__Blt; break;
+ case ILCode.__Bne_Un_S: code = ILCode.__Bne_Un; break;
+ case ILCode.__Bge_Un_S: code = ILCode.__Bge_Un; break;
+ case ILCode.__Bgt_Un_S: code = ILCode.__Bgt_Un; break;
+ case ILCode.__Ble_Un_S: code = ILCode.__Ble_Un; break;
+ case ILCode.__Blt_Un_S: code = ILCode.__Blt_Un; break;
+ case ILCode.__Leave_S: code = ILCode.Leave; break;
+ case ILCode.__Ldind_I: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.IntPtr; break;
+ case ILCode.__Ldind_I1: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.SByte; break;
+ case ILCode.__Ldind_I2: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Int16; break;
+ case ILCode.__Ldind_I4: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Int32; break;
+ case ILCode.__Ldind_I8: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Int64; break;
+ case ILCode.__Ldind_U1: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Byte; break;
+ case ILCode.__Ldind_U2: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.UInt16; break;
+ case ILCode.__Ldind_U4: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.UInt32; break;
+ case ILCode.__Ldind_R4: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Single; break;
+ case ILCode.__Ldind_R8: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Double; break;
+ case ILCode.__Stind_I: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.IntPtr; break;
+ case ILCode.__Stind_I1: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Byte; break;
+ case ILCode.__Stind_I2: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Int16; break;
+ case ILCode.__Stind_I4: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Int32; break;
+ case ILCode.__Stind_I8: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Int64; break;
+ case ILCode.__Stind_R4: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Single; break;
+ case ILCode.__Stind_R8: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Double; break;
+ }
+ }
+
+ public static ParameterDefinition GetParameter (this MethodBody self, int index)
+ {
+ var method = self.Method;
+
+ if (method.HasThis) {
+ if (index == 0)
+ return self.ThisParameter;
+
+ index--;
+ }
+
+ var parameters = method.Parameters;
+
+ if (index < 0 || index >= parameters.Count)
+ return null;
+
+ return parameters [index];
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/ILAst/ILInlining.cs b/ICSharpCode.Decompiler/ILAst/ILInlining.cs
new file mode 100644
index 00000000..11140ef0
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/ILInlining.cs
@@ -0,0 +1,524 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Mono.Cecil;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ /// <summary>
+ /// Performs inlining transformations.
+ /// </summary>
+ public class ILInlining
+ {
+ readonly ILBlock method;
+ internal Dictionary<ILVariable, int> numStloc = new Dictionary<ILVariable, int>();
+ internal Dictionary<ILVariable, int> numLdloc = new Dictionary<ILVariable, int>();
+ internal Dictionary<ILVariable, int> numLdloca = new Dictionary<ILVariable, int>();
+
+ public ILInlining(ILBlock method)
+ {
+ this.method = method;
+ AnalyzeMethod();
+ }
+
+ void AnalyzeMethod()
+ {
+ numStloc.Clear();
+ numLdloc.Clear();
+ numLdloca.Clear();
+
+ // Analyse the whole method
+ AnalyzeNode(method);
+ }
+
+ /// <summary>
+ /// For each variable reference, adds <paramref name="direction"/> to the num* dicts.
+ /// Direction will be 1 for analysis, and -1 when removing a node from analysis.
+ /// </summary>
+ void AnalyzeNode(ILNode node, int direction = 1)
+ {
+ ILExpression expr = node as ILExpression;
+ if (expr != null) {
+ ILVariable locVar = expr.Operand as ILVariable;
+ if (locVar != null) {
+ if (expr.Code == ILCode.Stloc) {
+ numStloc[locVar] = numStloc.GetOrDefault(locVar) + direction;
+ } else if (expr.Code == ILCode.Ldloc) {
+ numLdloc[locVar] = numLdloc.GetOrDefault(locVar) + direction;
+ } else if (expr.Code == ILCode.Ldloca) {
+ numLdloca[locVar] = numLdloca.GetOrDefault(locVar) + direction;
+ } else {
+ throw new NotSupportedException(expr.Code.ToString());
+ }
+ }
+ foreach (ILExpression child in expr.Arguments)
+ AnalyzeNode(child, direction);
+ } else {
+ var catchBlock = node as ILTryCatchBlock.CatchBlock;
+ if (catchBlock != null && catchBlock.ExceptionVariable != null) {
+ numStloc[catchBlock.ExceptionVariable] = numStloc.GetOrDefault(catchBlock.ExceptionVariable) + direction;
+ }
+
+ foreach (ILNode child in node.GetChildren())
+ AnalyzeNode(child, direction);
+ }
+ }
+
+ public bool InlineAllVariables()
+ {
+ bool modified = false;
+ ILInlining i = new ILInlining(method);
+ foreach (ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>())
+ modified |= i.InlineAllInBlock(block);
+ return modified;
+ }
+
+ public bool InlineAllInBlock(ILBlock block)
+ {
+ bool modified = false;
+ List<ILNode> body = block.Body;
+ if (block is ILTryCatchBlock.CatchBlock && body.Count > 1) {
+ ILVariable v = ((ILTryCatchBlock.CatchBlock)block).ExceptionVariable;
+ if (v != null && v.IsGenerated) {
+ if (numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 1 && numLdloc.GetOrDefault(v) == 1) {
+ ILVariable v2;
+ ILExpression ldException;
+ if (body[0].Match(ILCode.Stloc, out v2, out ldException) && ldException.MatchLdloc(v)) {
+ body.RemoveAt(0);
+ ((ILTryCatchBlock.CatchBlock)block).ExceptionVariable = v2;
+ modified = true;
+ }
+ }
+ }
+ }
+ for(int i = 0; i < body.Count - 1;) {
+ ILVariable locVar;
+ ILExpression expr;
+ if (body[i].Match(ILCode.Stloc, out locVar, out expr) && InlineOneIfPossible(block.Body, i, aggressive: false)) {
+ modified = true;
+ i = Math.Max(0, i - 1); // Go back one step
+ } else {
+ i++;
+ }
+ }
+ foreach(ILBasicBlock bb in body.OfType<ILBasicBlock>()) {
+ modified |= InlineAllInBasicBlock(bb);
+ }
+ return modified;
+ }
+
+ public bool InlineAllInBasicBlock(ILBasicBlock bb)
+ {
+ bool modified = false;
+ List<ILNode> body = bb.Body;
+ for(int i = 0; i < body.Count;) {
+ ILVariable locVar;
+ ILExpression expr;
+ if (body[i].Match(ILCode.Stloc, out locVar, out expr) && InlineOneIfPossible(bb.Body, i, aggressive: false)) {
+ modified = true;
+ i = Math.Max(0, i - 1); // Go back one step
+ } else {
+ i++;
+ }
+ }
+ return modified;
+ }
+
+ /// <summary>
+ /// Inlines instructions before pos into block.Body[pos].
+ /// </summary>
+ /// <returns>The number of instructions that were inlined.</returns>
+ public int InlineInto(List<ILNode> body, int pos, bool aggressive)
+ {
+ if (pos >= body.Count)
+ return 0;
+ int count = 0;
+ while (--pos >= 0) {
+ ILExpression expr = body[pos] as ILExpression;
+ if (expr == null || expr.Code != ILCode.Stloc)
+ break;
+ if (InlineOneIfPossible(body, pos, aggressive))
+ count++;
+ else
+ break;
+ }
+ return count;
+ }
+
+ /// <summary>
+ /// Aggressively inlines the stloc instruction at block.Body[pos] into the next instruction, if possible.
+ /// If inlining was possible; we will continue to inline (non-aggressively) into the the combined instruction.
+ /// </summary>
+ /// <remarks>
+ /// After the operation, pos will point to the new combined instruction.
+ /// </remarks>
+ public bool InlineIfPossible(List<ILNode> body, ref int pos)
+ {
+ if (InlineOneIfPossible(body, pos, true)) {
+ pos -= InlineInto(body, pos, false);
+ return true;
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Inlines the stloc instruction at block.Body[pos] into the next instruction, if possible.
+ /// </summary>
+ public bool InlineOneIfPossible(List<ILNode> body, int pos, bool aggressive)
+ {
+ ILVariable v;
+ ILExpression inlinedExpression;
+ if (body[pos].Match(ILCode.Stloc, out v, out inlinedExpression) && !v.IsPinned) {
+ if (InlineIfPossible(v, inlinedExpression, body.ElementAtOrDefault(pos+1), aggressive)) {
+ // Assign the ranges of the stloc instruction:
+ inlinedExpression.ILRanges.AddRange(((ILExpression)body[pos]).ILRanges);
+ // Remove the stloc instruction:
+ body.RemoveAt(pos);
+ return true;
+ } else if (numLdloc.GetOrDefault(v) == 0 && numLdloca.GetOrDefault(v) == 0) {
+ // The variable is never loaded
+ if (inlinedExpression.HasNoSideEffects()) {
+ // Remove completely
+ AnalyzeNode(body[pos], -1);
+ body.RemoveAt(pos);
+ return true;
+ } else if (inlinedExpression.CanBeExpressionStatement() && v.IsGenerated) {
+ // Assign the ranges of the stloc instruction:
+ inlinedExpression.ILRanges.AddRange(((ILExpression)body[pos]).ILRanges);
+ // Remove the stloc, but keep the inner expression
+ body[pos] = inlinedExpression;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Inlines 'expr' into 'next', if possible.
+ /// </summary>
+ bool InlineIfPossible(ILVariable v, ILExpression inlinedExpression, ILNode next, bool aggressive)
+ {
+ // ensure the variable is accessed only a single time
+ if (numStloc.GetOrDefault(v) != 1)
+ return false;
+ int ldloc = numLdloc.GetOrDefault(v);
+ if (ldloc > 1 || ldloc + numLdloca.GetOrDefault(v) != 1)
+ return false;
+
+ if (next is ILCondition)
+ next = ((ILCondition)next).Condition;
+ else if (next is ILWhileLoop)
+ next = ((ILWhileLoop)next).Condition;
+
+ ILExpression parent;
+ int pos;
+ if (FindLoadInNext(next as ILExpression, v, inlinedExpression, out parent, out pos) == true) {
+ if (ldloc == 0) {
+ if (!IsGeneratedValueTypeTemporary((ILExpression)next, parent, pos, v, inlinedExpression))
+ return false;
+ } else {
+ if (!aggressive && !v.IsGenerated && !NonAggressiveInlineInto((ILExpression)next, parent, inlinedExpression))
+ return false;
+ }
+
+ // Assign the ranges of the ldloc instruction:
+ inlinedExpression.ILRanges.AddRange(parent.Arguments[pos].ILRanges);
+
+ if (ldloc == 0) {
+ // it was an ldloca instruction, so we need to use the pseudo-opcode 'addressof' so that the types
+ // comes out correctly
+ parent.Arguments[pos] = new ILExpression(ILCode.AddressOf, null, inlinedExpression);
+ } else {
+ parent.Arguments[pos] = inlinedExpression;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Is this a temporary variable generated by the C# compiler for instance method calls on value type values
+ /// </summary>
+ /// <param name="next">The next top-level expression</param>
+ /// <param name="parent">The direct parent of the load within 'next'</param>
+ /// <param name="pos">Index of the load within 'parent'</param>
+ /// <param name="v">The variable being inlined.</param>
+ /// <param name="inlinedExpression">The expression being inlined</param>
+ bool IsGeneratedValueTypeTemporary(ILExpression next, ILExpression parent, int pos, ILVariable v, ILExpression inlinedExpression)
+ {
+ if (pos == 0 && v.Type != null && v.Type.IsValueType) {
+ // Inlining a value type variable is allowed only if the resulting code will maintain the semantics
+ // that the method is operating on a copy.
+ // Thus, we have to disallow inlining of other locals, fields, array elements, dereferenced pointers
+ switch (inlinedExpression.Code) {
+ case ILCode.Ldloc:
+ case ILCode.Stloc:
+ case ILCode.CompoundAssignment:
+ case ILCode.Ldelem_Any:
+ case ILCode.Ldelem_I:
+ case ILCode.Ldelem_I1:
+ case ILCode.Ldelem_I2:
+ case ILCode.Ldelem_I4:
+ case ILCode.Ldelem_I8:
+ case ILCode.Ldelem_R4:
+ case ILCode.Ldelem_R8:
+ case ILCode.Ldelem_Ref:
+ case ILCode.Ldelem_U1:
+ case ILCode.Ldelem_U2:
+ case ILCode.Ldelem_U4:
+ case ILCode.Ldobj:
+ case ILCode.Ldind_Ref:
+ return false;
+ case ILCode.Ldfld:
+ case ILCode.Stfld:
+ case ILCode.Ldsfld:
+ case ILCode.Stsfld:
+ // allow inlining field access only if it's a readonly field
+ FieldDefinition f = ((FieldReference)inlinedExpression.Operand).Resolve();
+ if (!(f != null && f.IsInitOnly))
+ return false;
+ break;
+ case ILCode.Call:
+ case ILCode.CallGetter:
+ // inlining runs both before and after IntroducePropertyAccessInstructions,
+ // so we have to handle both 'call' and 'callgetter'
+ MethodReference mr = (MethodReference)inlinedExpression.Operand;
+ // ensure that it's not an multi-dimensional array getter
+ if (mr.DeclaringType is ArrayType)
+ return false;
+ goto case ILCode.Callvirt;
+ case ILCode.Callvirt:
+ case ILCode.CallvirtGetter:
+ // don't inline foreach loop variables:
+ mr = (MethodReference)inlinedExpression.Operand;
+ if (mr.Name == "get_Current" && mr.HasThis)
+ return false;
+ break;
+ case ILCode.Castclass:
+ case ILCode.Unbox_Any:
+ // These are valid, but might occur as part of a foreach loop variable.
+ ILExpression arg = inlinedExpression.Arguments[0];
+ if (arg.Code == ILCode.CallGetter || arg.Code == ILCode.CallvirtGetter || arg.Code == ILCode.Call || arg.Code == ILCode.Callvirt) {
+ mr = (MethodReference)arg.Operand;
+ if (mr.Name == "get_Current" && mr.HasThis)
+ return false; // looks like a foreach loop variable, so don't inline it
+ }
+ break;
+ }
+
+ // inline the compiler-generated variable that are used when accessing a member on a value type:
+ switch (parent.Code) {
+ case ILCode.Call:
+ case ILCode.CallGetter:
+ case ILCode.CallSetter:
+ case ILCode.Callvirt:
+ case ILCode.CallvirtGetter:
+ case ILCode.CallvirtSetter:
+ MethodReference mr = (MethodReference)parent.Operand;
+ return mr.HasThis;
+ case ILCode.Stfld:
+ case ILCode.Ldfld:
+ case ILCode.Ldflda:
+ case ILCode.Await:
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Determines whether a variable should be inlined in non-aggressive mode, even though it is not a generated variable.
+ /// </summary>
+ /// <param name="next">The next top-level expression</param>
+ /// <param name="parent">The direct parent of the load within 'next'</param>
+ /// <param name="inlinedExpression">The expression being inlined</param>
+ bool NonAggressiveInlineInto(ILExpression next, ILExpression parent, ILExpression inlinedExpression)
+ {
+ if (inlinedExpression.Code == ILCode.DefaultValue)
+ return true;
+
+ switch (next.Code) {
+ case ILCode.Ret:
+ case ILCode.Brtrue:
+ return parent == next;
+ case ILCode.Switch:
+ return parent == next || (parent.Code == ILCode.Sub && parent == next.Arguments[0]);
+ default:
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Gets whether 'expressionBeingMoved' can be inlined into 'expr'.
+ /// </summary>
+ public bool CanInlineInto(ILExpression expr, ILVariable v, ILExpression expressionBeingMoved)
+ {
+ ILExpression parent;
+ int pos;
+ return FindLoadInNext(expr, v, expressionBeingMoved, out parent, out pos) == true;
+ }
+
+ /// <summary>
+ /// Finds the position to inline to.
+ /// </summary>
+ /// <returns>true = found; false = cannot continue search; null = not found</returns>
+ bool? FindLoadInNext(ILExpression expr, ILVariable v, ILExpression expressionBeingMoved, out ILExpression parent, out int pos)
+ {
+ parent = null;
+ pos = 0;
+ if (expr == null)
+ return false;
+ for (int i = 0; i < expr.Arguments.Count; i++) {
+ // Stop when seeing an opcode that does not guarantee that its operands will be evaluated.
+ // Inlining in that case might result in the inlined expresion not being evaluted.
+ if (i == 1 && (expr.Code == ILCode.LogicAnd || expr.Code == ILCode.LogicOr || expr.Code == ILCode.TernaryOp || expr.Code == ILCode.NullCoalescing))
+ return false;
+
+ ILExpression arg = expr.Arguments[i];
+
+ if ((arg.Code == ILCode.Ldloc || arg.Code == ILCode.Ldloca) && arg.Operand == v) {
+ parent = expr;
+ pos = i;
+ return true;
+ }
+ bool? r = FindLoadInNext(arg, v, expressionBeingMoved, out parent, out pos);
+ if (r != null)
+ return r;
+ }
+ if (IsSafeForInlineOver(expr, expressionBeingMoved))
+ return null; // continue searching
+ else
+ return false; // abort, inlining not possible
+ }
+
+ /// <summary>
+ /// Determines whether it is safe to move 'expressionBeingMoved' past 'expr'
+ /// </summary>
+ bool IsSafeForInlineOver(ILExpression expr, ILExpression expressionBeingMoved)
+ {
+ switch (expr.Code) {
+ case ILCode.Ldloc:
+ ILVariable loadedVar = (ILVariable)expr.Operand;
+ if (numLdloca.GetOrDefault(loadedVar) != 0) {
+ // abort, inlining is not possible
+ return false;
+ }
+ foreach (ILExpression potentialStore in expressionBeingMoved.GetSelfAndChildrenRecursive<ILExpression>()) {
+ if (potentialStore.Code == ILCode.Stloc && potentialStore.Operand == loadedVar)
+ return false;
+ }
+ // the expression is loading a non-forbidden variable
+ return true;
+ case ILCode.Ldloca:
+ case ILCode.Ldflda:
+ case ILCode.Ldsflda:
+ case ILCode.Ldelema:
+ case ILCode.AddressOf:
+ case ILCode.ValueOf:
+ case ILCode.NullableOf:
+ // address-loading instructions are safe if their arguments are safe
+ foreach (ILExpression arg in expr.Arguments) {
+ if (!IsSafeForInlineOver(arg, expressionBeingMoved))
+ return false;
+ }
+ return true;
+ default:
+ // instructions with no side-effects are safe (except for Ldloc and Ldloca which are handled separately)
+ return expr.HasNoSideEffects();
+ }
+ }
+
+ /// <summary>
+ /// Runs a very simple form of copy propagation.
+ /// Copy propagation is used in two cases:
+ /// 1) assignments from arguments to local variables
+ /// If the target variable is assigned to only once (so always is that argument) and the argument is never changed (no ldarga/starg),
+ /// then we can replace the variable with the argument.
+ /// 2) assignments of address-loading instructions to local variables
+ /// </summary>
+ public void CopyPropagation()
+ {
+ foreach (ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) {
+ for (int i = 0; i < block.Body.Count; i++) {
+ ILVariable v;
+ ILExpression copiedExpr;
+ if (block.Body[i].Match(ILCode.Stloc, out v, out copiedExpr)
+ && !v.IsParameter && numStloc.GetOrDefault(v) == 1 && numLdloca.GetOrDefault(v) == 0
+ && CanPerformCopyPropagation(copiedExpr, v))
+ {
+ // un-inline the arguments of the ldArg instruction
+ ILVariable[] uninlinedArgs = new ILVariable[copiedExpr.Arguments.Count];
+ for (int j = 0; j < uninlinedArgs.Length; j++) {
+ uninlinedArgs[j] = new ILVariable { IsGenerated = true, Name = v.Name + "_cp_" + j };
+ block.Body.Insert(i++, new ILExpression(ILCode.Stloc, uninlinedArgs[j], copiedExpr.Arguments[j]));
+ }
+
+ // perform copy propagation:
+ foreach (var expr in method.GetSelfAndChildrenRecursive<ILExpression>()) {
+ if (expr.Code == ILCode.Ldloc && expr.Operand == v) {
+ expr.Code = copiedExpr.Code;
+ expr.Operand = copiedExpr.Operand;
+ for (int j = 0; j < uninlinedArgs.Length; j++) {
+ expr.Arguments.Add(new ILExpression(ILCode.Ldloc, uninlinedArgs[j]));
+ }
+ }
+ }
+
+ block.Body.RemoveAt(i);
+ if (uninlinedArgs.Length > 0) {
+ // if we un-inlined stuff; we need to update the usage counters
+ AnalyzeMethod();
+ }
+ InlineInto(block.Body, i, aggressive: false); // maybe inlining gets possible after the removal of block.Body[i]
+ i -= uninlinedArgs.Length + 1;
+ }
+ }
+ }
+ }
+
+ bool CanPerformCopyPropagation(ILExpression expr, ILVariable copyVariable)
+ {
+ switch (expr.Code) {
+ case ILCode.Ldloca:
+ case ILCode.Ldelema:
+ case ILCode.Ldflda:
+ case ILCode.Ldsflda:
+ // All address-loading instructions always return the same value for a given operand/argument combination,
+ // so they can be safely copied.
+ return true;
+ case ILCode.Ldloc:
+ ILVariable v = (ILVariable)expr.Operand;
+ if (v.IsParameter) {
+ // Parameters can be copied only if they aren't assigned to (directly or indirectly via ldarga)
+ return numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 0;
+ } else {
+ // Variables are be copied only if both they and the target copy variable are generated,
+ // and if the variable has only a single assignment
+ return v.IsGenerated && copyVariable.IsGenerated && numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 1;
+ }
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs
new file mode 100644
index 00000000..4a4a8138
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs
@@ -0,0 +1,543 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Mono.Cecil;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ /// <summary>
+ /// IL AST transformation that introduces array, object and collection initializers.
+ /// </summary>
+ partial class ILAstOptimizer
+ {
+ #region Array Initializers
+ bool TransformArrayInitializers(List<ILNode> body, ILExpression expr, int pos)
+ {
+ ILVariable v, v3;
+ ILExpression newarrExpr;
+ TypeReference elementType;
+ ILExpression lengthExpr;
+ int arrayLength;
+ if (expr.Match(ILCode.Stloc, out v, out newarrExpr) &&
+ newarrExpr.Match(ILCode.Newarr, out elementType, out lengthExpr) &&
+ lengthExpr.Match(ILCode.Ldc_I4, out arrayLength) &&
+ arrayLength > 0) {
+ ILExpression[] newArr;
+ int initArrayPos;
+ if (ForwardScanInitializeArrayRuntimeHelper(body, pos + 1, v, elementType, arrayLength, out newArr, out initArrayPos)) {
+ var arrayType = new ArrayType(elementType, 1);
+ arrayType.Dimensions[0] = new ArrayDimension(0, arrayLength);
+ body[pos] = new ILExpression(ILCode.Stloc, v, new ILExpression(ILCode.InitArray, arrayType, newArr));
+ body.RemoveAt(initArrayPos);
+ }
+ // Put in a limit so that we don't consume too much memory if the code allocates a huge array
+ // and populates it extremely sparsly. However, 255 "null" elements in a row actually occur in the Mono C# compiler!
+ const int maxConsecutiveDefaultValueExpressions = 300;
+ List<ILExpression> operands = new List<ILExpression>();
+ int numberOfInstructionsToRemove = 0;
+ for (int j = pos + 1; j < body.Count; j++) {
+ ILExpression nextExpr = body[j] as ILExpression;
+ int arrayPos;
+ if (nextExpr != null &&
+ nextExpr.Code.IsStoreToArray() &&
+ nextExpr.Arguments[0].Match(ILCode.Ldloc, out v3) &&
+ v == v3 &&
+ nextExpr.Arguments[1].Match(ILCode.Ldc_I4, out arrayPos) &&
+ arrayPos >= operands.Count &&
+ arrayPos <= operands.Count + maxConsecutiveDefaultValueExpressions &&
+ !nextExpr.Arguments[2].ContainsReferenceTo(v3))
+ {
+ while (operands.Count < arrayPos)
+ operands.Add(new ILExpression(ILCode.DefaultValue, elementType));
+ operands.Add(nextExpr.Arguments[2]);
+ numberOfInstructionsToRemove++;
+ } else {
+ break;
+ }
+ }
+ if (operands.Count == arrayLength) {
+ var arrayType = new ArrayType(elementType, 1);
+ arrayType.Dimensions[0] = new ArrayDimension(0, arrayLength);
+ expr.Arguments[0] = new ILExpression(ILCode.InitArray, arrayType, operands);
+ body.RemoveRange(pos + 1, numberOfInstructionsToRemove);
+
+ new ILInlining(method).InlineIfPossible(body, ref pos);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool TransformMultidimensionalArrayInitializers(List<ILNode> body, ILExpression expr, int pos)
+ {
+ ILVariable v;
+ ILExpression newarrExpr;
+ MethodReference ctor;
+ List<ILExpression> ctorArgs;
+ ArrayType arrayType;
+ if (expr.Match(ILCode.Stloc, out v, out newarrExpr) &&
+ newarrExpr.Match(ILCode.Newobj, out ctor, out ctorArgs) &&
+ (arrayType = (ctor.DeclaringType as ArrayType)) != null &&
+ arrayType.Rank == ctorArgs.Count) {
+ // Clone the type, so we can muck about with the Dimensions
+ arrayType = new ArrayType(arrayType.ElementType, arrayType.Rank);
+ var arrayLengths = new int[arrayType.Rank];
+ for (int i = 0; i < arrayType.Rank; i++) {
+ if (!ctorArgs[i].Match(ILCode.Ldc_I4, out arrayLengths[i])) return false;
+ if (arrayLengths[i] <= 0) return false;
+ arrayType.Dimensions[i] = new ArrayDimension(0, arrayLengths[i]);
+ }
+
+ var totalElements = arrayLengths.Aggregate(1, (t, l) => t * l);
+ ILExpression[] newArr;
+ int initArrayPos;
+ if (ForwardScanInitializeArrayRuntimeHelper(body, pos + 1, v, arrayType, totalElements, out newArr, out initArrayPos)) {
+ body[pos] = new ILExpression(ILCode.Stloc, v, new ILExpression(ILCode.InitArray, arrayType, newArr));
+ body.RemoveAt(initArrayPos);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool ForwardScanInitializeArrayRuntimeHelper(List<ILNode> body, int pos, ILVariable array, TypeReference arrayType, int arrayLength, out ILExpression[] values, out int foundPos)
+ {
+ ILVariable v2;
+ MethodReference methodRef;
+ ILExpression methodArg1;
+ ILExpression methodArg2;
+ FieldReference fieldRef;
+ if (body.ElementAtOrDefault(pos).Match(ILCode.Call, out methodRef, out methodArg1, out methodArg2) &&
+ methodRef.DeclaringType.FullName == "System.Runtime.CompilerServices.RuntimeHelpers" &&
+ methodRef.Name == "InitializeArray" &&
+ methodArg1.Match(ILCode.Ldloc, out v2) &&
+ array == v2 &&
+ methodArg2.Match(ILCode.Ldtoken, out fieldRef))
+ {
+ FieldDefinition fieldDef = fieldRef.ResolveWithinSameModule();
+ if (fieldDef != null && fieldDef.InitialValue != null) {
+ ILExpression[] newArr = new ILExpression[arrayLength];
+ if (DecodeArrayInitializer(arrayType.GetElementType(), fieldDef.InitialValue, newArr))
+ {
+ values = newArr;
+ foundPos = pos;
+ return true;
+ }
+ }
+ }
+ values = null;
+ foundPos = -1;
+ return false;
+ }
+
+ static bool DecodeArrayInitializer(TypeReference elementTypeRef, byte[] initialValue, ILExpression[] output)
+ {
+ TypeCode elementType = TypeAnalysis.GetTypeCode(elementTypeRef);
+ switch (elementType) {
+ case TypeCode.Boolean:
+ case TypeCode.Byte:
+ return DecodeArrayInitializer(initialValue, output, elementType, (d, i) => (int)d[i]);
+ case TypeCode.SByte:
+ return DecodeArrayInitializer(initialValue, output, elementType, (d, i) => (int)unchecked((sbyte)d[i]));
+ case TypeCode.Int16:
+ return DecodeArrayInitializer(initialValue, output, elementType, (d, i) => (int)BitConverter.ToInt16(d, i));
+ case TypeCode.Char:
+ case TypeCode.UInt16:
+ return DecodeArrayInitializer(initialValue, output, elementType, (d, i) => (int)BitConverter.ToUInt16(d, i));
+ case TypeCode.Int32:
+ case TypeCode.UInt32:
+ return DecodeArrayInitializer(initialValue, output, elementType, BitConverter.ToInt32);
+ case TypeCode.Int64:
+ case TypeCode.UInt64:
+ return DecodeArrayInitializer(initialValue, output, elementType, BitConverter.ToInt64);
+ case TypeCode.Single:
+ return DecodeArrayInitializer(initialValue, output, elementType, BitConverter.ToSingle);
+ case TypeCode.Double:
+ return DecodeArrayInitializer(initialValue, output, elementType, BitConverter.ToDouble);
+ case TypeCode.Object:
+ var typeDef = elementTypeRef.ResolveWithinSameModule();
+ if (typeDef != null && typeDef.IsEnum)
+ return DecodeArrayInitializer(typeDef.GetEnumUnderlyingType(), initialValue, output);
+
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ static bool DecodeArrayInitializer<T>(byte[] initialValue, ILExpression[] output, TypeCode elementType, Func<byte[], int, T> decoder)
+ {
+ int elementSize = ElementSizeOf(elementType);
+ if (initialValue.Length < (output.Length * elementSize))
+ return false;
+
+ ILCode code = LoadCodeFor(elementType);
+ for (int i = 0; i < output.Length; i++)
+ output[i] = new ILExpression(code, decoder(initialValue, i * elementSize));
+
+ return true;
+ }
+
+ static ILCode LoadCodeFor(TypeCode elementType)
+ {
+ switch (elementType) {
+ case TypeCode.Boolean:
+ case TypeCode.Byte:
+ case TypeCode.SByte:
+ case TypeCode.Char:
+ case TypeCode.Int16:
+ case TypeCode.UInt16:
+ case TypeCode.Int32:
+ case TypeCode.UInt32:
+ return ILCode.Ldc_I4;
+ case TypeCode.Int64:
+ case TypeCode.UInt64:
+ return ILCode.Ldc_I8;
+ case TypeCode.Single:
+ return ILCode.Ldc_R4;
+ case TypeCode.Double:
+ return ILCode.Ldc_R8;
+ default:
+ throw new ArgumentOutOfRangeException("elementType");
+ }
+ }
+
+ static int ElementSizeOf(TypeCode elementType)
+ {
+ switch (elementType) {
+ case TypeCode.Boolean:
+ case TypeCode.Byte:
+ case TypeCode.SByte:
+ return 1;
+ case TypeCode.Char:
+ case TypeCode.Int16:
+ case TypeCode.UInt16:
+ return 2;
+ case TypeCode.Int32:
+ case TypeCode.UInt32:
+ case TypeCode.Single:
+ return 4;
+ case TypeCode.Int64:
+ case TypeCode.UInt64:
+ case TypeCode.Double:
+ return 8;
+ default:
+ throw new ArgumentOutOfRangeException("elementType");
+ }
+ }
+ #endregion
+
+ /// <summary>
+ /// Handles both object and collection initializers.
+ /// </summary>
+ bool TransformObjectInitializers(List<ILNode> body, ILExpression expr, int pos)
+ {
+ if (!context.Settings.ObjectOrCollectionInitializers)
+ return false;
+
+ Debug.Assert(body[pos] == expr); // should be called for top-level expressions only
+ ILVariable v;
+ ILExpression newObjExpr;
+ TypeReference newObjType;
+ bool isValueType;
+ MethodReference ctor;
+ List<ILExpression> ctorArgs;
+ if (expr.Match(ILCode.Stloc, out v, out newObjExpr)) {
+ if (newObjExpr.Match(ILCode.Newobj, out ctor, out ctorArgs)) {
+ // v = newObj(ctor, ctorArgs)
+ newObjType = ctor.DeclaringType;
+ isValueType = false;
+ } else if (newObjExpr.Match(ILCode.DefaultValue, out newObjType)) {
+ // v = defaultvalue(type)
+ isValueType = true;
+ } else {
+ return false;
+ }
+ } else if (expr.Match(ILCode.Call, out ctor, out ctorArgs)) {
+ // call(SomeStruct::.ctor, ldloca(v), remainingArgs)
+ if (ctorArgs.Count > 0 && ctorArgs[0].Match(ILCode.Ldloca, out v)) {
+ isValueType = true;
+ newObjType = ctor.DeclaringType;
+ ctorArgs = new List<ILExpression>(ctorArgs);
+ ctorArgs.RemoveAt(0);
+ newObjExpr = new ILExpression(ILCode.Newobj, ctor, ctorArgs);
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ if (newObjType.IsValueType != isValueType)
+ return false;
+
+ int originalPos = pos;
+
+ // don't use object initializer syntax for closures
+ if (Ast.Transforms.DelegateConstruction.IsPotentialClosure(context, newObjType.ResolveWithinSameModule()))
+ return false;
+
+ ILExpression initializer = ParseObjectInitializer(body, ref pos, v, newObjExpr, IsCollectionType(newObjType), isValueType);
+
+ if (initializer.Arguments.Count == 1) // only newobj argument, no initializer elements
+ return false;
+ int totalElementCount = pos - originalPos - 1; // totalElementCount: includes elements from nested collections
+ Debug.Assert(totalElementCount >= initializer.Arguments.Count - 1);
+
+ // Verify that we can inline 'v' into the next instruction:
+
+ if (pos >= body.Count)
+ return false; // reached end of block, but there should be another instruction which consumes the initialized object
+
+ ILInlining inlining = new ILInlining(method);
+ if (isValueType) {
+ // one ldloc for the use of the initialized object
+ if (inlining.numLdloc.GetOrDefault(v) != 1)
+ return false;
+ // one ldloca for each initializer argument, and also for the ctor call (if it exists)
+ if (inlining.numLdloca.GetOrDefault(v) != totalElementCount + (expr.Code == ILCode.Call ? 1 : 0))
+ return false;
+ // one stloc for the initial store (if no ctor call was used)
+ if (inlining.numStloc.GetOrDefault(v) != (expr.Code == ILCode.Call ? 0 : 1))
+ return false;
+ } else {
+ // one ldloc for each initializer argument, and another ldloc for the use of the initialized object
+ if (inlining.numLdloc.GetOrDefault(v) != totalElementCount + 1)
+ return false;
+ if (!(inlining.numStloc.GetOrDefault(v) == 1 && inlining.numLdloca.GetOrDefault(v) == 0))
+ return false;
+ }
+ ILExpression nextExpr = body[pos] as ILExpression;
+ if (!inlining.CanInlineInto(nextExpr, v, initializer))
+ return false;
+
+ if (expr.Code == ILCode.Stloc) {
+ expr.Arguments[0] = initializer;
+ } else {
+ Debug.Assert(expr.Code == ILCode.Call);
+ expr.Code = ILCode.Stloc;
+ expr.Operand = v;
+ expr.Arguments.Clear();
+ expr.Arguments.Add(initializer);
+ }
+ // remove all the instructions that were pulled into the initializer
+ body.RemoveRange(originalPos + 1, pos - originalPos - 1);
+
+ // now that we know that it's an object initializer, change all the first arguments to 'InitializedObject'
+ ChangeFirstArgumentToInitializedObject(initializer);
+
+ inlining = new ILInlining(method);
+ inlining.InlineIfPossible(body, ref originalPos);
+
+ return true;
+ }
+
+ /// <summary>
+ /// Gets whether the type supports collection initializers.
+ /// </summary>
+ static bool IsCollectionType(TypeReference tr)
+ {
+ if (tr == null)
+ return false;
+ TypeDefinition td = tr.Resolve();
+ while (td != null) {
+ if (td.Interfaces.Any(intf => intf.Name == "IEnumerable" && intf.Namespace == "System.Collections"))
+ return true;
+ td = td.BaseType != null ? td.BaseType.Resolve() : null;
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Gets whether 'expr' represents a setter in an object initializer.
+ /// ('CallvirtSetter(Property, v, value)')
+ /// </summary>
+ static bool IsSetterInObjectInitializer(ILExpression expr)
+ {
+ if (expr == null)
+ return false;
+ if (expr.Code == ILCode.CallvirtSetter || expr.Code == ILCode.CallSetter || expr.Code == ILCode.Stfld) {
+ return expr.Arguments.Count == 2;
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Gets whether 'expr' represents the invocation of an 'Add' method in a collection initializer.
+ /// </summary>
+ static bool IsAddMethodCall(ILExpression expr)
+ {
+ MethodReference addMethod;
+ List<ILExpression> args;
+ if (expr.Match(ILCode.Callvirt, out addMethod, out args) || expr.Match(ILCode.Call, out addMethod, out args)) {
+ if (addMethod.Name == "Add" && addMethod.HasThis) {
+ return args.Count >= 2;
+ }
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Parses an object initializer.
+ /// </summary>
+ /// <param name="body">ILAst block</param>
+ /// <param name="pos">
+ /// Input: position of the instruction assigning to 'v'.
+ /// Output: first position after the object initializer
+ /// </param>
+ /// <param name="v">The variable that holds the object being initialized</param>
+ /// <param name="newObjExpr">The newobj instruction</param>
+ /// <returns>InitObject instruction</returns>
+ ILExpression ParseObjectInitializer(List<ILNode> body, ref int pos, ILVariable v, ILExpression newObjExpr, bool isCollection, bool isValueType)
+ {
+ // Take care not to modify any existing ILExpressions in here.
+ // We just construct new ones around the old ones, any modifications must wait until the whole
+ // object/collection initializer was analyzed.
+ ILExpression objectInitializer = new ILExpression(isCollection ? ILCode.InitCollection : ILCode.InitObject, null, newObjExpr);
+ List<ILExpression> initializerStack = new List<ILExpression>();
+ initializerStack.Add(objectInitializer);
+ while (++pos < body.Count) {
+ ILExpression nextExpr = body[pos] as ILExpression;
+ if (IsSetterInObjectInitializer(nextExpr)) {
+ if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, false, isValueType)) {
+ CleanupInitializerStackAfterFailedAdjustment(initializerStack);
+ break;
+ }
+ initializerStack[initializerStack.Count - 1].Arguments.Add(nextExpr);
+ } else if (IsAddMethodCall(nextExpr)) {
+ if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, true, isValueType)) {
+ CleanupInitializerStackAfterFailedAdjustment(initializerStack);
+ break;
+ }
+ initializerStack[initializerStack.Count - 1].Arguments.Add(nextExpr);
+ } else {
+ // can't match any more initializers: end of object initializer
+ break;
+ }
+ }
+ return objectInitializer;
+ }
+
+ static bool AdjustInitializerStack(List<ILExpression> initializerStack, ILExpression argument, ILVariable v, bool isCollection, bool isValueType)
+ {
+ // Argument is of the form 'getter(getter(...(v)))'
+ // Unpack it into a list of getters:
+ List<ILExpression> getters = new List<ILExpression>();
+ while (argument.Code == ILCode.CallvirtGetter || argument.Code == ILCode.CallGetter || argument.Code == ILCode.Ldfld) {
+ getters.Add(argument);
+ if (argument.Arguments.Count != 1)
+ return false;
+ argument = argument.Arguments[0];
+ }
+ // Ensure that the final argument is 'v'
+ if (isValueType) {
+ ILVariable loadedVar;
+ if (!(argument.Match(ILCode.Ldloca, out loadedVar) && loadedVar == v))
+ return false;
+ } else {
+ if (!argument.MatchLdloc(v))
+ return false;
+ }
+ // Now compare the getters with those that are currently active on the initializer stack:
+ int i;
+ for (i = 1; i <= Math.Min(getters.Count, initializerStack.Count - 1); i++) {
+ ILExpression g1 = initializerStack[i].Arguments[0]; // getter stored in initializer
+ ILExpression g2 = getters[getters.Count - i]; // matching getter from argument
+ if (g1.Operand != g2.Operand) {
+ // operands differ, so we abort the comparison
+ break;
+ }
+ }
+ // Remove all initializers from the stack that were not matched with one from the argument:
+ initializerStack.RemoveRange(i, initializerStack.Count - i);
+ // Now create new initializers for the remaining arguments:
+ for (; i <= getters.Count; i++) {
+ ILExpression g = getters[getters.Count - i];
+ MemberReference mr = (MemberReference)g.Operand;
+ TypeReference returnType;
+ if (mr is FieldReference)
+ returnType = TypeAnalysis.GetFieldType((FieldReference)mr);
+ else
+ returnType = TypeAnalysis.SubstituteTypeArgs(((MethodReference)mr).ReturnType, mr);
+
+ ILExpression nestedInitializer = new ILExpression(
+ IsCollectionType(returnType) ? ILCode.InitCollection : ILCode.InitObject,
+ null, g);
+ // add new initializer to its parent:
+ ILExpression parentInitializer = initializerStack[initializerStack.Count - 1];
+ if (parentInitializer.Code == ILCode.InitCollection) {
+ // can't add children to collection initializer
+ if (parentInitializer.Arguments.Count == 1) {
+ // convert empty collection initializer to object initializer
+ parentInitializer.Code = ILCode.InitObject;
+ } else {
+ return false;
+ }
+ }
+ parentInitializer.Arguments.Add(nestedInitializer);
+ initializerStack.Add(nestedInitializer);
+ }
+ ILExpression lastInitializer = initializerStack[initializerStack.Count - 1];
+ if (isCollection) {
+ return lastInitializer.Code == ILCode.InitCollection;
+ } else {
+ if (lastInitializer.Code == ILCode.InitCollection) {
+ if (lastInitializer.Arguments.Count == 1) {
+ // convert empty collection initializer to object initializer
+ lastInitializer.Code = ILCode.InitObject;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return true;
+ }
+ }
+ }
+
+ static void CleanupInitializerStackAfterFailedAdjustment(List<ILExpression> initializerStack)
+ {
+ // There might be empty nested initializers left over; so we'll remove those:
+ while (initializerStack.Count > 1 && initializerStack[initializerStack.Count - 1].Arguments.Count == 1) {
+ ILExpression parent = initializerStack[initializerStack.Count - 2];
+ Debug.Assert(parent.Arguments.Last() == initializerStack[initializerStack.Count - 1]);
+ parent.Arguments.RemoveAt(parent.Arguments.Count - 1);
+ initializerStack.RemoveAt(initializerStack.Count - 1);
+ }
+ }
+
+ static void ChangeFirstArgumentToInitializedObject(ILExpression initializer)
+ {
+ // Go through all elements in the initializer (so skip the newobj-instr. at the start)
+ for (int i = 1; i < initializer.Arguments.Count; i++) {
+ ILExpression element = initializer.Arguments[i];
+ if (element.Code == ILCode.InitCollection || element.Code == ILCode.InitObject) {
+ // nested collection/object initializer
+ ILExpression getCollection = element.Arguments[0];
+ getCollection.Arguments[0] = new ILExpression(ILCode.InitializedObject, null);
+ ChangeFirstArgumentToInitializedObject(element); // handle the collection elements
+ } else {
+ element.Arguments[0] = new ILExpression(ILCode.InitializedObject, null);
+ }
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/ILAst/LiftedOperators.cs b/ICSharpCode.Decompiler/ILAst/LiftedOperators.cs
new file mode 100644
index 00000000..9edeac74
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/LiftedOperators.cs
@@ -0,0 +1,528 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Mono.Cecil;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ partial class ILAstOptimizer
+ {
+ bool SimplifyLiftedOperators(List<ILNode> body, ILExpression expr, int pos)
+ {
+ if (!new PatternMatcher(typeSystem).SimplifyLiftedOperators(expr)) return false;
+
+ var inlining = new ILInlining(method);
+ while (--pos >= 0 && inlining.InlineIfPossible(body, ref pos)) ;
+
+ return true;
+ }
+
+ sealed class PatternMatcher
+ {
+ readonly TypeSystem typeSystem;
+ public PatternMatcher(TypeSystem typeSystem)
+ {
+ this.typeSystem = typeSystem;
+ }
+
+ public bool SimplifyLiftedOperators(ILExpression expr)
+ {
+ if (Simplify(expr)) return true;
+
+ bool modified = false;
+ foreach (var a in expr.Arguments)
+ modified |= SimplifyLiftedOperators(a);
+ return modified;
+ }
+
+ abstract class Pattern
+ {
+ public readonly Pattern[] Arguments;
+
+ protected Pattern(Pattern[] arguments)
+ {
+ Arguments = arguments;
+ }
+
+ public virtual bool Match(PatternMatcher pm, ILExpression e)
+ {
+ if (e.Arguments.Count != Arguments.Length || e.Prefixes != null) return false;
+ for (int i = 0; i < Arguments.Length; i++)
+ if (!Arguments[i].Match(pm, e.Arguments[i])) return false;
+ return true;
+ }
+
+ public virtual ILExpression BuildNew(PatternMatcher pm)
+ {
+ throw new NotSupportedException();
+ }
+
+ public static Pattern operator &(Pattern a, Pattern b)
+ {
+ return new ILPattern(ILCode.LogicAnd, a, b);
+ }
+
+ public static Pattern operator |(Pattern a, Pattern b)
+ {
+ return new ILPattern(ILCode.LogicOr, a, b);
+ }
+
+ public static Pattern operator !(Pattern a)
+ {
+ return new ILPattern(ILCode.LogicNot, a);
+ }
+ }
+
+ sealed class ILPattern : Pattern
+ {
+ readonly ILCode code;
+
+ public ILPattern(ILCode code, params Pattern[] arguments)
+ : base(arguments)
+ {
+ this.code = code;
+ }
+
+ public override bool Match(PatternMatcher pm, ILExpression e)
+ {
+ return e.Code == code && base.Match(pm, e);
+ }
+
+ public override ILExpression BuildNew(PatternMatcher pm)
+ {
+ var args = new ILExpression[Arguments.Length];
+ for (int i = 0; i < args.Length; i++) args[i] = Arguments[i].BuildNew(pm);
+ TypeReference t = null;
+ switch (code) {
+ case ILCode.Ceq:
+ case ILCode.Cne:
+ t = pm.typeSystem.Boolean;
+ break;
+ case ILCode.NullCoalescing:
+ t = args[1].InferredType;
+ break;
+ }
+ return new ILExpression(code, null, args) { InferredType = t };
+ }
+ }
+
+ sealed class MethodPattern : Pattern
+ {
+ readonly ILCode code;
+ readonly string method;
+
+ public MethodPattern(ILCode code, string method, params Pattern[] arguments)
+ : base(arguments)
+ {
+ this.code = code;
+ this.method = method;
+ }
+
+ public override bool Match(PatternMatcher pm, ILExpression e)
+ {
+ if (e.Code != code) return false;
+ var m = (MethodReference)e.Operand;
+ return m.Name == method && TypeAnalysis.IsNullableType(m.DeclaringType) && base.Match(pm, e);
+ }
+ }
+
+ enum OperatorType
+ {
+ Equality, InEquality, Comparison, Other
+ }
+
+ sealed class OperatorPattern : Pattern
+ {
+ OperatorType type;
+ bool simple;
+
+ public OperatorPattern() : base(null) { }
+
+ public OperatorPattern(OperatorType type, bool simple)
+ : this()
+ {
+ this.type = type;
+ this.simple = simple;
+ }
+
+ public override bool Match(PatternMatcher pm, ILExpression e)
+ {
+ switch (e.Code) {
+ case ILCode.Ceq:
+ if (type != OperatorType.Equality) return false;
+ break;
+ case ILCode.Cne:
+ if (type != OperatorType.InEquality) return false;
+ break;
+ case ILCode.Cgt:
+ case ILCode.Cgt_Un:
+ case ILCode.Cge:
+ case ILCode.Cge_Un:
+ case ILCode.Clt:
+ case ILCode.Clt_Un:
+ case ILCode.Cle:
+ case ILCode.Cle_Un:
+ if (type != OperatorType.Comparison) return false;
+ break;
+ case ILCode.Add:
+ case ILCode.Add_Ovf:
+ case ILCode.Add_Ovf_Un:
+ case ILCode.Sub:
+ case ILCode.Sub_Ovf:
+ case ILCode.Sub_Ovf_Un:
+ case ILCode.Mul:
+ case ILCode.Mul_Ovf:
+ case ILCode.Mul_Ovf_Un:
+ case ILCode.Div:
+ case ILCode.Div_Un:
+ case ILCode.Rem:
+ case ILCode.Rem_Un:
+ case ILCode.And:
+ case ILCode.Or:
+ case ILCode.Xor:
+ case ILCode.Shl:
+ case ILCode.Shr:
+ case ILCode.Shr_Un:
+ case ILCode.Not:
+ case ILCode.Neg:
+ case ILCode.LogicNot:
+ if (type != OperatorType.Other) return false;
+ break;
+ case ILCode.Call:
+ var m = e.Operand as MethodReference;
+ if (m == null || m.HasThis || !m.HasParameters || e.Arguments.Count > 2 || !IsCustomOperator(m.Name)) return false;
+ break;
+ default: return false;
+ }
+ if (pm.Operator != null) throw new InvalidOperationException();
+ pm.Operator = e;
+
+ var a0 = e.Arguments[0];
+ if (!simple) return VariableAGetValueOrDefault.Match(pm, a0) && VariableBGetValueOrDefault.Match(pm, e.Arguments[1]);
+ if (e.Arguments.Count == 1) return VariableAGetValueOrDefault.Match(pm, a0);
+ if (VariableAGetValueOrDefault.Match(pm, a0)) {
+ pm.SimpleOperand = e.Arguments[1];
+ pm.SimpleLeftOperand = false;
+ return true;
+ }
+ if (VariableAGetValueOrDefault.Match(pm, e.Arguments[1])) {
+ pm.SimpleOperand = a0;
+ pm.SimpleLeftOperand = true;
+ return true;
+ }
+ return false;
+ }
+
+ bool IsCustomOperator(string s)
+ {
+ switch (type) {
+ case OperatorType.Equality: return s == "op_Equality";
+ case OperatorType.InEquality: return s == "op_Inequality";
+ case OperatorType.Comparison:
+ if (s.Length < 11 || !s.StartsWith("op_", StringComparison.Ordinal)) return false;
+ switch (s) {
+ case "op_GreaterThan":
+ case "op_GreaterThanOrEqual":
+ case "op_LessThan":
+ case "op_LessThanOrEqual": return true;
+ default: return false;
+ }
+ default:
+ if (s.Length < 10 || !s.StartsWith("op_", StringComparison.Ordinal)) return false;
+ switch (s) {
+ case "op_Addition":
+ case "op_Subtraction":
+ case "op_Multiply":
+ case "op_Division":
+ case "op_Modulus":
+ case "op_BitwiseAnd":
+ case "op_BitwiseOr":
+ case "op_ExclusiveOr":
+ case "op_LeftShift":
+ case "op_RightShift":
+ case "op_UnaryNegation":
+ case "op_UnaryPlus":
+ case "op_LogicalNot":
+ case "op_OnesComplement":
+ case "op_Increment":
+ case "op_Decrement": return true;
+ default: return false;
+ }
+ }
+ }
+
+ public override ILExpression BuildNew(PatternMatcher pm)
+ {
+ var res = pm.Operator;
+ res.Arguments.Clear();
+ if (pm.SimpleLeftOperand) res.Arguments.Add(pm.SimpleOperand);
+ res.Arguments.Add(VariableA.BuildNew(pm));
+ if (pm.B != null) res.Arguments.Add(VariableB.BuildNew(pm));
+ else if (pm.SimpleOperand != null && !pm.SimpleLeftOperand) res.Arguments.Add(pm.SimpleOperand);
+ return res;
+ }
+ }
+
+ sealed class AnyPattern : Pattern
+ {
+ public AnyPattern() : base(null) { }
+
+ public override bool Match(PatternMatcher pm, ILExpression e)
+ {
+ if (pm.SimpleOperand != null) throw new InvalidOperationException();
+ pm.SimpleOperand = e;
+ return true;
+ }
+
+ public override ILExpression BuildNew(PatternMatcher pm)
+ {
+ return pm.SimpleOperand;
+ }
+ }
+
+ sealed class VariablePattern : Pattern
+ {
+ readonly ILCode code;
+ readonly bool b;
+
+ public VariablePattern(ILCode code, bool b)
+ : base(null)
+ {
+ this.code = code;
+ this.b = b;
+ }
+
+ public override bool Match(PatternMatcher pm, ILExpression e)
+ {
+ if (e.Code != code) return false;
+ var v = e.Operand as ILVariable;
+ return v != null && (b ? Capture(ref pm.B, v) : Capture(ref pm.A, v));
+ }
+
+ static bool Capture(ref ILVariable pmvar, ILVariable v)
+ {
+ if (pmvar != null) return pmvar == v;
+ pmvar = v;
+ return true;
+ }
+
+ static readonly ILExpression[] EmptyArguments = new ILExpression[0];
+ public override ILExpression BuildNew(PatternMatcher pm)
+ {
+ var v = b ? pm.B : pm.A;
+ var e = new ILExpression(ILCode.Ldloc, v, EmptyArguments);
+ if (TypeAnalysis.IsNullableType(v.Type)) e = new ILExpression(ILCode.ValueOf, null, e);
+ return e;
+ }
+ }
+
+ sealed class BooleanPattern : Pattern
+ {
+ public static readonly Pattern False = new BooleanPattern(false), True = new BooleanPattern(true);
+
+ readonly object value;
+ BooleanPattern(bool value)
+ : base(null)
+ {
+ this.value = Convert.ToInt32(value);
+ }
+
+ public override bool Match(PatternMatcher pm, ILExpression e)
+ {
+ return e.Code == ILCode.Ldc_I4 && TypeAnalysis.IsBoolean(e.InferredType) && Equals(e.Operand, value);
+ }
+
+ public override ILExpression BuildNew(PatternMatcher pm)
+ {
+ // boolean constants are wrapped inside a container to disable simplyfication of equality comparisons
+ return new ILExpression(ILCode.Wrap, null, new ILExpression(ILCode.Ldc_I4, value));
+ }
+ }
+
+ static readonly Pattern VariableRefA = new VariablePattern(ILCode.Ldloca, false), VariableRefB = new VariablePattern(ILCode.Ldloca, true);
+ static readonly Pattern VariableA = new VariablePattern(ILCode.Ldloc, false), VariableB = new VariablePattern(ILCode.Ldloc, true);
+ static readonly Pattern VariableAHasValue = new MethodPattern(ILCode.CallGetter, "get_HasValue", VariableRefA);
+ static readonly Pattern VariableAGetValueOrDefault = new MethodPattern(ILCode.Call, "GetValueOrDefault", VariableRefA);
+ static readonly Pattern VariableBHasValue = new MethodPattern(ILCode.CallGetter, "get_HasValue", VariableRefB);
+ static readonly Pattern VariableBGetValueOrDefault = new MethodPattern(ILCode.Call, "GetValueOrDefault", VariableRefB);
+ static readonly Pattern CeqHasValue = new ILPattern(ILCode.Ceq, VariableAHasValue, VariableBHasValue);
+ static readonly Pattern CneHasValue = new ILPattern(ILCode.Cne, VariableAHasValue, VariableBHasValue);
+ static readonly Pattern AndHasValue = new ILPattern(ILCode.And, VariableAHasValue, VariableBHasValue);
+ static readonly Pattern Any = new AnyPattern();
+ static readonly Pattern OperatorVariableAB = new OperatorPattern();
+
+ static OperatorPattern OperatorNN(OperatorType type)
+ {
+ return new OperatorPattern(type, false);
+ }
+
+ static OperatorPattern OperatorNV(OperatorType type)
+ {
+ return new OperatorPattern(type, true);
+ }
+
+ static Pattern NewObj(Pattern p)
+ {
+ return new MethodPattern(ILCode.Newobj, ".ctor", p);
+ }
+
+ static readonly Pattern[] Comparisons = new Pattern[] {
+ /* both operands nullable */
+ // == (primitive, decimal)
+ OperatorNN(OperatorType.Equality) & CeqHasValue,
+ // == (struct)
+ CeqHasValue & (!VariableAHasValue | OperatorNN(OperatorType.Equality)),
+ // != (primitive, decimal)
+ OperatorNN(OperatorType.InEquality) | CneHasValue,
+ // != (struct)
+ CneHasValue | (VariableAHasValue & OperatorNN(OperatorType.InEquality)),
+ // > , < , >= , <= (primitive, decimal)
+ OperatorNN(OperatorType.Comparison) & AndHasValue,
+ // > , < , >= , <= (struct)
+ AndHasValue & OperatorNN(OperatorType.Comparison),
+
+ /* only one operand nullable */
+ // == (primitive, decimal)
+ OperatorNV(OperatorType.Equality) & VariableAHasValue,
+ // == (struct)
+ VariableAHasValue & OperatorNV(OperatorType.Equality),
+ // != (primitive, decimal)
+ OperatorNV(OperatorType.InEquality) | !VariableAHasValue,
+ // != (struct)
+ !VariableAHasValue | OperatorNV(OperatorType.InEquality),
+ // > , <, >= , <= (primitive, decimal)
+ OperatorNV(OperatorType.Comparison) & VariableAHasValue,
+ // > , < , >= , <= (struct)
+ VariableAHasValue & OperatorNV(OperatorType.Comparison),
+ };
+
+ static readonly Pattern[] Other = new Pattern[] {
+ /* both operands nullable */
+ // & (bool)
+ new ILPattern(ILCode.TernaryOp, VariableAGetValueOrDefault | (!VariableBGetValueOrDefault & !VariableAHasValue), VariableB, VariableA),
+ new ILPattern(ILCode.And, VariableA, VariableB),
+ // | (bool)
+ new ILPattern(ILCode.TernaryOp, VariableAGetValueOrDefault | (!VariableBGetValueOrDefault & !VariableAHasValue), VariableA, VariableB),
+ new ILPattern(ILCode.Or, VariableA, VariableB),
+ // null coalescing
+ new ILPattern(ILCode.TernaryOp, VariableAHasValue, NewObj(VariableAGetValueOrDefault), VariableB),
+ new ILPattern(ILCode.NullCoalescing, VariableA, VariableB),
+ // all other
+ new ILPattern(ILCode.TernaryOp, AndHasValue, NewObj(OperatorNN(OperatorType.Other)), new ILPattern(ILCode.DefaultValue)),
+ OperatorVariableAB,
+
+ /* only one operand nullable */
+ // & (bool)
+ new ILPattern(ILCode.TernaryOp, Any, VariableA, NewObj(BooleanPattern.False)),
+ new ILPattern(ILCode.And, VariableA, Any),
+ // | (bool)
+ new ILPattern(ILCode.TernaryOp, Any, NewObj(BooleanPattern.True), VariableA),
+ new ILPattern(ILCode.Or, VariableA, Any),
+ // == true
+ VariableAGetValueOrDefault & VariableAHasValue,
+ new ILPattern(ILCode.Ceq, VariableA, BooleanPattern.True),
+ // != true
+ !VariableAGetValueOrDefault | !VariableAHasValue,
+ new ILPattern(ILCode.Cne, VariableA, BooleanPattern.True),
+ // == false
+ !VariableAGetValueOrDefault & VariableAHasValue,
+ new ILPattern(ILCode.Ceq, VariableA, BooleanPattern.False),
+ // != false
+ VariableAGetValueOrDefault | !VariableAHasValue,
+ new ILPattern(ILCode.Cne, VariableA, BooleanPattern.False),
+ // ?? true
+ !VariableAHasValue | VariableAGetValueOrDefault,
+ new ILPattern(ILCode.NullCoalescing, VariableA, BooleanPattern.True),
+ // ?? false
+ VariableAHasValue & VariableAGetValueOrDefault,
+ new ILPattern(ILCode.NullCoalescing, VariableA, BooleanPattern.False),
+ // null coalescing
+ new ILPattern(ILCode.TernaryOp, VariableAHasValue, VariableAGetValueOrDefault, Any),
+ new ILPattern(ILCode.NullCoalescing, VariableA, Any),
+ // all other
+ new ILPattern(ILCode.TernaryOp, VariableAHasValue, NewObj(OperatorNV(OperatorType.Other)), new ILPattern(ILCode.DefaultValue)),
+ OperatorVariableAB,
+ };
+
+ ILVariable A, B;
+ ILExpression Operator, SimpleOperand;
+ bool SimpleLeftOperand;
+
+ void Reset()
+ {
+ A = null;
+ B = null;
+ Operator = null;
+ SimpleOperand = null;
+ SimpleLeftOperand = false;
+ }
+
+ bool Simplify(ILExpression expr)
+ {
+ if (expr.Code == ILCode.TernaryOp || expr.Code == ILCode.LogicAnd || expr.Code == ILCode.LogicOr) {
+ Pattern[] ps;
+ if (expr.Code != ILCode.TernaryOp) {
+ ps = Comparisons;
+ for (int i = 0; i < ps.Length; i++) {
+ Reset();
+ if (!ps[i].Match(this, expr)) continue;
+ SetResult(expr, OperatorVariableAB.BuildNew(this));
+ return true;
+ }
+ }
+ ps = Other;
+ for (int i = 0; i < ps.Length; i += 2) {
+ Reset();
+ if (!ps[i].Match(this, expr)) continue;
+ var n = ps[i + 1].BuildNew(this);
+ SetResult(expr, n);
+ if (n.Code == ILCode.NullCoalescing) {
+ // if both operands are nullable then the result is also nullable
+ if (n.Arguments[1].Code == ILCode.ValueOf) {
+ n.Arguments[0] = n.Arguments[0].Arguments[0];
+ n.Arguments[1] = n.Arguments[1].Arguments[0];
+ }
+ } else if (n.Code != ILCode.Ceq && n.Code != ILCode.Cne) {
+ expr.Code = ILCode.NullableOf;
+ expr.InferredType = expr.ExpectedType = null;
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static void SetResult(ILExpression expr, ILExpression n)
+ {
+ // IL ranges from removed nodes are assigned to the new operator expression
+ var removednodes = expr.GetSelfAndChildrenRecursive<ILExpression>().Except(n.GetSelfAndChildrenRecursive<ILExpression>());
+ n.ILRanges.AddRange(removednodes.SelectMany(el => el.ILRanges));
+ // the new expression is wrapped in a container so that negations aren't pushed through lifted comparison operations
+ expr.Code = ILCode.Wrap;
+ expr.Arguments.Clear();
+ expr.Arguments.Add(n);
+ expr.ILRanges.Clear();
+ expr.InferredType = n.InferredType;
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/ILAst/LoopsAndConditions.cs b/ICSharpCode.Decompiler/ILAst/LoopsAndConditions.cs
new file mode 100644
index 00000000..32095b03
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/LoopsAndConditions.cs
@@ -0,0 +1,443 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using ICSharpCode.Decompiler.FlowAnalysis;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ /// <summary>
+ /// Description of LoopsAndConditions.
+ /// </summary>
+ public class LoopsAndConditions
+ {
+ Dictionary<ILLabel, ControlFlowNode> labelToCfNode = new Dictionary<ILLabel, ControlFlowNode>();
+
+ readonly DecompilerContext context;
+
+ uint nextLabelIndex = 0;
+
+ public LoopsAndConditions(DecompilerContext context)
+ {
+ this.context = context;
+ }
+
+ public void FindLoops(ILBlock block)
+ {
+ if (block.Body.Count > 0) {
+ ControlFlowGraph graph;
+ graph = BuildGraph(block.Body, (ILLabel)block.EntryGoto.Operand);
+ graph.ComputeDominance(context.CancellationToken);
+ graph.ComputeDominanceFrontier();
+ block.Body = FindLoops(new HashSet<ControlFlowNode>(graph.Nodes.Skip(3)), graph.EntryPoint, false);
+ }
+ }
+
+ public void FindConditions(ILBlock block)
+ {
+ if (block.Body.Count > 0) {
+ ControlFlowGraph graph;
+ graph = BuildGraph(block.Body, (ILLabel)block.EntryGoto.Operand);
+ graph.ComputeDominance(context.CancellationToken);
+ graph.ComputeDominanceFrontier();
+ block.Body = FindConditions(new HashSet<ControlFlowNode>(graph.Nodes.Skip(3)), graph.EntryPoint);
+ }
+ }
+
+ ControlFlowGraph BuildGraph(List<ILNode> nodes, ILLabel entryLabel)
+ {
+ int index = 0;
+ List<ControlFlowNode> cfNodes = new List<ControlFlowNode>();
+ ControlFlowNode entryPoint = new ControlFlowNode(index++, 0, ControlFlowNodeType.EntryPoint);
+ cfNodes.Add(entryPoint);
+ ControlFlowNode regularExit = new ControlFlowNode(index++, -1, ControlFlowNodeType.RegularExit);
+ cfNodes.Add(regularExit);
+ ControlFlowNode exceptionalExit = new ControlFlowNode(index++, -1, ControlFlowNodeType.ExceptionalExit);
+ cfNodes.Add(exceptionalExit);
+
+ // Create graph nodes
+ labelToCfNode = new Dictionary<ILLabel, ControlFlowNode>();
+ Dictionary<ILNode, ControlFlowNode> astNodeToCfNode = new Dictionary<ILNode, ControlFlowNode>();
+ foreach(ILBasicBlock node in nodes) {
+ ControlFlowNode cfNode = new ControlFlowNode(index++, -1, ControlFlowNodeType.Normal);
+ cfNodes.Add(cfNode);
+ astNodeToCfNode[node] = cfNode;
+ cfNode.UserData = node;
+
+ // Find all contained labels
+ foreach(ILLabel label in node.GetSelfAndChildrenRecursive<ILLabel>()) {
+ labelToCfNode[label] = cfNode;
+ }
+ }
+
+ // Entry endge
+ ControlFlowNode entryNode = labelToCfNode[entryLabel];
+ ControlFlowEdge entryEdge = new ControlFlowEdge(entryPoint, entryNode, JumpType.Normal);
+ entryPoint.Outgoing.Add(entryEdge);
+ entryNode.Incoming.Add(entryEdge);
+
+ // Create edges
+ foreach(ILBasicBlock node in nodes) {
+ ControlFlowNode source = astNodeToCfNode[node];
+
+ // Find all branches
+ foreach(ILLabel target in node.GetSelfAndChildrenRecursive<ILExpression>(e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())) {
+ ControlFlowNode destination;
+ // Labels which are out of out scope will not be in the collection
+ // Insert self edge only if we are sure we are a loop
+ if (labelToCfNode.TryGetValue(target, out destination) && (destination != source || target == node.Body.FirstOrDefault())) {
+ ControlFlowEdge edge = new ControlFlowEdge(source, destination, JumpType.Normal);
+ source.Outgoing.Add(edge);
+ destination.Incoming.Add(edge);
+ }
+ }
+ }
+
+ return new ControlFlowGraph(cfNodes.ToArray());
+ }
+
+ List<ILNode> FindLoops(HashSet<ControlFlowNode> scope, ControlFlowNode entryPoint, bool excludeEntryPoint)
+ {
+ List<ILNode> result = new List<ILNode>();
+
+ // Do not modify entry data
+ scope = new HashSet<ControlFlowNode>(scope);
+
+ Queue<ControlFlowNode> agenda = new Queue<ControlFlowNode>();
+ agenda.Enqueue(entryPoint);
+ while(agenda.Count > 0) {
+ ControlFlowNode node = agenda.Dequeue();
+
+ // If the node is a loop header
+ if (scope.Contains(node)
+ && node.DominanceFrontier.Contains(node)
+ && (node != entryPoint || !excludeEntryPoint))
+ {
+ HashSet<ControlFlowNode> loopContents = FindLoopContent(scope, node);
+
+ // If the first expression is a loop condition
+ ILBasicBlock basicBlock = (ILBasicBlock)node.UserData;
+ ILExpression condExpr;
+ ILLabel trueLabel;
+ ILLabel falseLabel;
+ // It has to be just brtrue - any preceding code would introduce goto
+ if(basicBlock.MatchSingleAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel))
+ {
+ ControlFlowNode trueTarget;
+ labelToCfNode.TryGetValue(trueLabel, out trueTarget);
+ ControlFlowNode falseTarget;
+ labelToCfNode.TryGetValue(falseLabel, out falseTarget);
+
+ // If one point inside the loop and the other outside
+ if ((!loopContents.Contains(trueTarget) && loopContents.Contains(falseTarget)) ||
+ (loopContents.Contains(trueTarget) && !loopContents.Contains(falseTarget)) )
+ {
+ loopContents.RemoveOrThrow(node);
+ scope.RemoveOrThrow(node);
+
+ // If false means enter the loop
+ if (loopContents.Contains(falseTarget) || falseTarget == node)
+ {
+ // Negate the condition
+ condExpr = new ILExpression(ILCode.LogicNot, null, condExpr);
+ ILLabel tmp = trueLabel;
+ trueLabel = falseLabel;
+ falseLabel = tmp;
+ }
+
+ ControlFlowNode postLoopTarget;
+ labelToCfNode.TryGetValue(falseLabel, out postLoopTarget);
+ if (postLoopTarget != null) {
+ // Pull more nodes into the loop
+ HashSet<ControlFlowNode> postLoopContents = FindDominatedNodes(scope, postLoopTarget);
+ var pullIn = scope.Except(postLoopContents).Where(n => node.Dominates(n));
+ loopContents.UnionWith(pullIn);
+ }
+
+ // Use loop to implement the brtrue
+ basicBlock.Body.RemoveTail(ILCode.Brtrue, ILCode.Br);
+ basicBlock.Body.Add(new ILWhileLoop() {
+ Condition = condExpr,
+ BodyBlock = new ILBlock() {
+ EntryGoto = new ILExpression(ILCode.Br, trueLabel),
+ Body = FindLoops(loopContents, node, false)
+ }
+ });
+ basicBlock.Body.Add(new ILExpression(ILCode.Br, falseLabel));
+ result.Add(basicBlock);
+
+ scope.ExceptWith(loopContents);
+ }
+ }
+
+ // Fallback method: while(true)
+ if (scope.Contains(node)) {
+ result.Add(new ILBasicBlock() {
+ Body = new List<ILNode>() {
+ new ILLabel() { Name = "Loop_" + (nextLabelIndex++) },
+ new ILWhileLoop() {
+ BodyBlock = new ILBlock() {
+ EntryGoto = new ILExpression(ILCode.Br, (ILLabel)basicBlock.Body.First()),
+ Body = FindLoops(loopContents, node, true)
+ }
+ },
+ },
+ });
+
+ scope.ExceptWith(loopContents);
+ }
+ }
+
+ // Using the dominator tree should ensure we find the the widest loop first
+ foreach(var child in node.DominatorTreeChildren) {
+ agenda.Enqueue(child);
+ }
+ }
+
+ // Add whatever is left
+ foreach(var node in scope) {
+ result.Add((ILNode)node.UserData);
+ }
+ scope.Clear();
+
+ return result;
+ }
+
+ List<ILNode> FindConditions(HashSet<ControlFlowNode> scope, ControlFlowNode entryNode)
+ {
+ List<ILNode> result = new List<ILNode>();
+
+ // Do not modify entry data
+ scope = new HashSet<ControlFlowNode>(scope);
+
+ Stack<ControlFlowNode> agenda = new Stack<ControlFlowNode>();
+ agenda.Push(entryNode);
+ while(agenda.Count > 0) {
+ ControlFlowNode node = agenda.Pop();
+
+ // Find a block that represents a simple condition
+ if (scope.Contains(node)) {
+
+ ILBasicBlock block = (ILBasicBlock)node.UserData;
+
+ {
+ // Switch
+ ILLabel[] caseLabels;
+ ILExpression switchArg;
+ ILLabel fallLabel;
+ if (block.MatchLastAndBr(ILCode.Switch, out caseLabels, out switchArg, out fallLabel)) {
+
+ // Replace the switch code with ILSwitch
+ ILSwitch ilSwitch = new ILSwitch() { Condition = switchArg };
+ block.Body.RemoveTail(ILCode.Switch, ILCode.Br);
+ block.Body.Add(ilSwitch);
+ block.Body.Add(new ILExpression(ILCode.Br, fallLabel));
+ result.Add(block);
+
+ // Remove the item so that it is not picked up as content
+ scope.RemoveOrThrow(node);
+
+ // Find the switch offset
+ int addValue = 0;
+ List<ILExpression> subArgs;
+ if (ilSwitch.Condition.Match(ILCode.Sub, out subArgs) && subArgs[1].Match(ILCode.Ldc_I4, out addValue)) {
+ ilSwitch.Condition = subArgs[0];
+ }
+
+ // Pull in code of cases
+ ControlFlowNode fallTarget = null;
+ labelToCfNode.TryGetValue(fallLabel, out fallTarget);
+
+ HashSet<ControlFlowNode> frontiers = new HashSet<ControlFlowNode>();
+ if (fallTarget != null)
+ frontiers.UnionWith(fallTarget.DominanceFrontier.Except(new [] { fallTarget }));
+
+ foreach(ILLabel condLabel in caseLabels) {
+ ControlFlowNode condTarget = null;
+ labelToCfNode.TryGetValue(condLabel, out condTarget);
+ if (condTarget != null)
+ frontiers.UnionWith(condTarget.DominanceFrontier.Except(new [] { condTarget }));
+ }
+
+ for (int i = 0; i < caseLabels.Length; i++) {
+ ILLabel condLabel = caseLabels[i];
+
+ // Find or create new case block
+ ILSwitch.CaseBlock caseBlock = ilSwitch.CaseBlocks.FirstOrDefault(b => b.EntryGoto.Operand == condLabel);
+ if (caseBlock == null) {
+ caseBlock = new ILSwitch.CaseBlock() {
+ Values = new List<int>(),
+ EntryGoto = new ILExpression(ILCode.Br, condLabel)
+ };
+ ilSwitch.CaseBlocks.Add(caseBlock);
+
+ ControlFlowNode condTarget = null;
+ labelToCfNode.TryGetValue(condLabel, out condTarget);
+ if (condTarget != null && !frontiers.Contains(condTarget)) {
+ HashSet<ControlFlowNode> content = FindDominatedNodes(scope, condTarget);
+ scope.ExceptWith(content);
+ caseBlock.Body.AddRange(FindConditions(content, condTarget));
+ // Add explicit break which should not be used by default, but the goto removal might decide to use it
+ caseBlock.Body.Add(new ILBasicBlock() {
+ Body = {
+ new ILLabel() { Name = "SwitchBreak_" + (nextLabelIndex++) },
+ new ILExpression(ILCode.LoopOrSwitchBreak, null)
+ }
+ });
+ }
+ }
+ caseBlock.Values.Add(i + addValue);
+ }
+
+ // Heuristis to determine if we want to use fallthough as default case
+ if (fallTarget != null && !frontiers.Contains(fallTarget)) {
+ HashSet<ControlFlowNode> content = FindDominatedNodes(scope, fallTarget);
+ if (content.Any()) {
+ var caseBlock = new ILSwitch.CaseBlock() { EntryGoto = new ILExpression(ILCode.Br, fallLabel) };
+ ilSwitch.CaseBlocks.Add(caseBlock);
+ block.Body.RemoveTail(ILCode.Br);
+
+ scope.ExceptWith(content);
+ caseBlock.Body.AddRange(FindConditions(content, fallTarget));
+ // Add explicit break which should not be used by default, but the goto removal might decide to use it
+ caseBlock.Body.Add(new ILBasicBlock() {
+ Body = {
+ new ILLabel() { Name = "SwitchBreak_" + (nextLabelIndex++) },
+ new ILExpression(ILCode.LoopOrSwitchBreak, null)
+ }
+ });
+ }
+ }
+ }
+
+ // Two-way branch
+ ILExpression condExpr;
+ ILLabel trueLabel;
+ ILLabel falseLabel;
+ if(block.MatchLastAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel)) {
+
+ // Swap bodies since that seems to be the usual C# order
+ ILLabel temp = trueLabel;
+ trueLabel = falseLabel;
+ falseLabel = temp;
+ condExpr = new ILExpression(ILCode.LogicNot, null, condExpr);
+
+ // Convert the brtrue to ILCondition
+ ILCondition ilCond = new ILCondition() {
+ Condition = condExpr,
+ TrueBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, trueLabel) },
+ FalseBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, falseLabel) }
+ };
+ block.Body.RemoveTail(ILCode.Brtrue, ILCode.Br);
+ block.Body.Add(ilCond);
+ result.Add(block);
+
+ // Remove the item immediately so that it is not picked up as content
+ scope.RemoveOrThrow(node);
+
+ ControlFlowNode trueTarget = null;
+ labelToCfNode.TryGetValue(trueLabel, out trueTarget);
+ ControlFlowNode falseTarget = null;
+ labelToCfNode.TryGetValue(falseLabel, out falseTarget);
+
+ // Pull in the conditional code
+ if (trueTarget != null && HasSingleEdgeEnteringBlock(trueTarget)) {
+ HashSet<ControlFlowNode> content = FindDominatedNodes(scope, trueTarget);
+ scope.ExceptWith(content);
+ ilCond.TrueBlock.Body.AddRange(FindConditions(content, trueTarget));
+ }
+ if (falseTarget != null && HasSingleEdgeEnteringBlock(falseTarget)) {
+ HashSet<ControlFlowNode> content = FindDominatedNodes(scope, falseTarget);
+ scope.ExceptWith(content);
+ ilCond.FalseBlock.Body.AddRange(FindConditions(content, falseTarget));
+ }
+ }
+ }
+
+ // Add the node now so that we have good ordering
+ if (scope.Contains(node)) {
+ result.Add((ILNode)node.UserData);
+ scope.Remove(node);
+ }
+ }
+
+ // depth-first traversal of dominator tree
+ for (int i = node.DominatorTreeChildren.Count - 1; i >= 0; i--) {
+ agenda.Push(node.DominatorTreeChildren[i]);
+ }
+ }
+
+ // Add whatever is left
+ foreach(var node in scope) {
+ result.Add((ILNode)node.UserData);
+ }
+
+ return result;
+ }
+
+ static bool HasSingleEdgeEnteringBlock(ControlFlowNode node)
+ {
+ return node.Incoming.Count(edge => !node.Dominates(edge.Source)) == 1;
+ }
+
+ static HashSet<ControlFlowNode> FindDominatedNodes(HashSet<ControlFlowNode> scope, ControlFlowNode head)
+ {
+ HashSet<ControlFlowNode> agenda = new HashSet<ControlFlowNode>();
+ HashSet<ControlFlowNode> result = new HashSet<ControlFlowNode>();
+ agenda.Add(head);
+
+ while(agenda.Count > 0) {
+ ControlFlowNode addNode = agenda.First();
+ agenda.Remove(addNode);
+
+ if (scope.Contains(addNode) && head.Dominates(addNode) && result.Add(addNode)) {
+ foreach (var successor in addNode.Successors) {
+ agenda.Add(successor);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ static HashSet<ControlFlowNode> FindLoopContent(HashSet<ControlFlowNode> scope, ControlFlowNode head)
+ {
+ var viaBackEdges = head.Predecessors.Where(p => head.Dominates(p));
+ HashSet<ControlFlowNode> agenda = new HashSet<ControlFlowNode>(viaBackEdges);
+ HashSet<ControlFlowNode> result = new HashSet<ControlFlowNode>();
+
+ while(agenda.Count > 0) {
+ ControlFlowNode addNode = agenda.First();
+ agenda.Remove(addNode);
+
+ if (scope.Contains(addNode) && head.Dominates(addNode) && result.Add(addNode)) {
+ foreach (var predecessor in addNode.Predecessors) {
+ agenda.Add(predecessor);
+ }
+ }
+ }
+ if (scope.Contains(head))
+ result.Add(head);
+
+ return result;
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/ILAst/PatternMatching.cs b/ICSharpCode.Decompiler/ILAst/PatternMatching.cs
new file mode 100644
index 00000000..441088b9
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/PatternMatching.cs
@@ -0,0 +1,177 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Mono.Cecil;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ public static class PatternMatching
+ {
+ public static bool Match(this ILNode node, ILCode code)
+ {
+ ILExpression expr = node as ILExpression;
+ return expr != null && expr.Prefixes == null && expr.Code == code;
+ }
+
+ public static bool Match<T>(this ILNode node, ILCode code, out T operand)
+ {
+ ILExpression expr = node as ILExpression;
+ if (expr != null && expr.Prefixes == null && expr.Code == code && expr.Arguments.Count == 0) {
+ operand = (T)expr.Operand;
+ return true;
+ }
+ operand = default(T);
+ return false;
+ }
+
+ public static bool Match(this ILNode node, ILCode code, out List<ILExpression> args)
+ {
+ ILExpression expr = node as ILExpression;
+ if (expr != null && expr.Prefixes == null && expr.Code == code) {
+ Debug.Assert(expr.Operand == null);
+ args = expr.Arguments;
+ return true;
+ }
+ args = null;
+ return false;
+ }
+
+ public static bool Match(this ILNode node, ILCode code, out ILExpression arg)
+ {
+ List<ILExpression> args;
+ if (node.Match(code, out args) && args.Count == 1) {
+ arg = args[0];
+ return true;
+ }
+ arg = null;
+ return false;
+ }
+
+ public static bool Match<T>(this ILNode node, ILCode code, out T operand, out List<ILExpression> args)
+ {
+ ILExpression expr = node as ILExpression;
+ if (expr != null && expr.Prefixes == null && expr.Code == code) {
+ operand = (T)expr.Operand;
+ args = expr.Arguments;
+ return true;
+ }
+ operand = default(T);
+ args = null;
+ return false;
+ }
+
+ public static bool Match<T>(this ILNode node, ILCode code, out T operand, out ILExpression arg)
+ {
+ List<ILExpression> args;
+ if (node.Match(code, out operand, out args) && args.Count == 1) {
+ arg = args[0];
+ return true;
+ }
+ arg = null;
+ return false;
+ }
+
+ public static bool Match<T>(this ILNode node, ILCode code, out T operand, out ILExpression arg1, out ILExpression arg2)
+ {
+ List<ILExpression> args;
+ if (node.Match(code, out operand, out args) && args.Count == 2) {
+ arg1 = args[0];
+ arg2 = args[1];
+ return true;
+ }
+ arg1 = null;
+ arg2 = null;
+ return false;
+ }
+
+ public static bool MatchSingle<T>(this ILBasicBlock bb, ILCode code, out T operand, out ILExpression arg)
+ {
+ if (bb.Body.Count == 2 &&
+ bb.Body[0] is ILLabel &&
+ bb.Body[1].Match(code, out operand, out arg))
+ {
+ return true;
+ }
+ operand = default(T);
+ arg = null;
+ return false;
+ }
+
+ public static bool MatchSingleAndBr<T>(this ILBasicBlock bb, ILCode code, out T operand, out ILExpression arg, out ILLabel brLabel)
+ {
+ if (bb.Body.Count == 3 &&
+ bb.Body[0] is ILLabel &&
+ bb.Body[1].Match(code, out operand, out arg) &&
+ bb.Body[2].Match(ILCode.Br, out brLabel))
+ {
+ return true;
+ }
+ operand = default(T);
+ arg = null;
+ brLabel = null;
+ return false;
+ }
+
+ public static bool MatchLastAndBr<T>(this ILBasicBlock bb, ILCode code, out T operand, out ILExpression arg, out ILLabel brLabel)
+ {
+ if (bb.Body.ElementAtOrDefault(bb.Body.Count - 2).Match(code, out operand, out arg) &&
+ bb.Body.LastOrDefault().Match(ILCode.Br, out brLabel))
+ {
+ return true;
+ }
+ operand = default(T);
+ arg = null;
+ brLabel = null;
+ return false;
+ }
+
+ public static bool MatchThis(this ILNode node)
+ {
+ ILVariable v;
+ return node.Match(ILCode.Ldloc, out v) && v.IsParameter && v.OriginalParameter.Index == -1;
+ }
+
+ public static bool MatchLdloc(this ILNode node, ILVariable expectedVar)
+ {
+ ILVariable v;
+ return node.Match(ILCode.Ldloc, out v) && v == expectedVar;
+ }
+
+ public static bool MatchLdloca(this ILNode node, ILVariable expectedVar)
+ {
+ ILVariable v;
+ return node.Match(ILCode.Ldloca, out v) && v == expectedVar;
+ }
+
+ public static bool MatchStloc(this ILNode node, ILVariable expectedVar, out ILExpression expr)
+ {
+ ILVariable v;
+ return node.Match(ILCode.Stloc, out v, out expr) && v == expectedVar;
+ }
+
+ public static bool MatchLdcI4(this ILNode node, int expectedValue)
+ {
+ int v;
+ return node.Match(ILCode.Ldc_I4, out v) && v == expectedValue;
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs
new file mode 100644
index 00000000..f514aa45
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs
@@ -0,0 +1,1103 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+using ICSharpCode.NRefactory.Utils;
+using Mono.Cecil;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ public partial class ILAstOptimizer
+ {
+ #region TypeConversionSimplifications
+ static bool TypeConversionSimplifications(List<ILNode> body, ILExpression expr, int pos)
+ {
+ bool modified = false;
+ modified |= TransformDecimalCtorToConstant(expr);
+ modified |= SimplifyLdcI4ConvI8(expr);
+ modified |= RemoveConvIFromArrayCreation(expr);
+ foreach(ILExpression arg in expr.Arguments) {
+ modified |= TypeConversionSimplifications(null, arg, -1);
+ }
+ return modified;
+ }
+
+ static bool TransformDecimalCtorToConstant(ILExpression expr)
+ {
+ MethodReference r;
+ List<ILExpression> args;
+ if (expr.Match(ILCode.Newobj, out r, out args) &&
+ r.DeclaringType.Namespace == "System" &&
+ r.DeclaringType.Name == "Decimal")
+ {
+ if (args.Count == 1) {
+ int val;
+ if (args[0].Match(ILCode.Ldc_I4, out val)) {
+ expr.Code = ILCode.Ldc_Decimal;
+ expr.Operand = new decimal(val);
+ expr.InferredType = r.DeclaringType;
+ expr.Arguments.Clear();
+ return true;
+ }
+ } else if (args.Count == 5) {
+ int lo, mid, hi, isNegative, scale;
+ if (expr.Arguments[0].Match(ILCode.Ldc_I4, out lo) &&
+ expr.Arguments[1].Match(ILCode.Ldc_I4, out mid) &&
+ expr.Arguments[2].Match(ILCode.Ldc_I4, out hi) &&
+ expr.Arguments[3].Match(ILCode.Ldc_I4, out isNegative) &&
+ expr.Arguments[4].Match(ILCode.Ldc_I4, out scale))
+ {
+ expr.Code = ILCode.Ldc_Decimal;
+ expr.Operand = new decimal(lo, mid, hi, isNegative != 0, (byte)scale);
+ expr.InferredType = r.DeclaringType;
+ expr.Arguments.Clear();
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ static bool SimplifyLdcI4ConvI8(ILExpression expr)
+ {
+ ILExpression ldc;
+ int val;
+ if (expr.Match(ILCode.Conv_I8, out ldc) && ldc.Match(ILCode.Ldc_I4, out val)) {
+ expr.Code = ILCode.Ldc_I8;
+ expr.Operand = (long)val;
+ expr.Arguments.Clear();
+ return true;
+ }
+ return false;
+ }
+
+ static bool RemoveConvIFromArrayCreation(ILExpression expr)
+ {
+ TypeReference typeRef;
+ ILExpression length;
+ ILExpression input;
+ if (expr.Match(ILCode.Newarr, out typeRef, out length)) {
+ if (length.Match(ILCode.Conv_Ovf_I, out input) || length.Match(ILCode.Conv_I, out input)
+ || length.Match(ILCode.Conv_Ovf_I_Un, out input) || length.Match(ILCode.Conv_U, out input))
+ {
+ expr.Arguments[0] = input;
+ return true;
+ }
+ }
+ return false;
+ }
+ #endregion
+
+ #region SimplifyLdObjAndStObj
+ static bool SimplifyLdObjAndStObj(List<ILNode> body, ILExpression expr, int pos)
+ {
+ bool modified = false;
+ expr = SimplifyLdObjAndStObj(expr, ref modified);
+ if (modified && body != null)
+ body[pos] = expr;
+ for (int i = 0; i < expr.Arguments.Count; i++) {
+ expr.Arguments[i] = SimplifyLdObjAndStObj(expr.Arguments[i], ref modified);
+ modified |= SimplifyLdObjAndStObj(null, expr.Arguments[i], -1);
+ }
+ return modified;
+ }
+
+ static ILExpression SimplifyLdObjAndStObj(ILExpression expr, ref bool modified)
+ {
+ if (expr.Code == ILCode.Initobj) {
+ expr.Code = ILCode.Stobj;
+ expr.Arguments.Add(new ILExpression(ILCode.DefaultValue, expr.Operand));
+ modified = true;
+ } else if (expr.Code == ILCode.Cpobj) {
+ expr.Code = ILCode.Stobj;
+ expr.Arguments[1] = new ILExpression(ILCode.Ldobj, expr.Operand, expr.Arguments[1]);
+ modified = true;
+ }
+ ILExpression arg, arg2;
+ TypeReference type;
+ ILCode? newCode = null;
+ if (expr.Match(ILCode.Stobj, out type, out arg, out arg2)) {
+ switch (arg.Code) {
+ case ILCode.Ldelema: newCode = ILCode.Stelem_Any; break;
+ case ILCode.Ldloca: newCode = ILCode.Stloc; break;
+ case ILCode.Ldflda: newCode = ILCode.Stfld; break;
+ case ILCode.Ldsflda: newCode = ILCode.Stsfld; break;
+ }
+ } else if (expr.Match(ILCode.Ldobj, out type, out arg)) {
+ switch (arg.Code) {
+ case ILCode.Ldelema: newCode = ILCode.Ldelem_Any; break;
+ case ILCode.Ldloca: newCode = ILCode.Ldloc; break;
+ case ILCode.Ldflda: newCode = ILCode.Ldfld; break;
+ case ILCode.Ldsflda: newCode = ILCode.Ldsfld; break;
+ }
+ }
+ if (newCode != null) {
+ arg.Code = newCode.Value;
+ if (expr.Code == ILCode.Stobj) {
+ arg.InferredType = expr.InferredType;
+ arg.ExpectedType = expr.ExpectedType;
+ arg.Arguments.Add(arg2);
+ }
+ arg.ILRanges.AddRange(expr.ILRanges);
+ modified = true;
+ return arg;
+ } else {
+ return expr;
+ }
+ }
+ #endregion
+
+ #region CachedDelegateInitialization
+ void CachedDelegateInitializationWithField(ILBlock block, ref int i)
+ {
+ // if (logicnot(ldsfld(field))) {
+ // stsfld(field, newobj(Action::.ctor, ldnull(), ldftn(method)))
+ // } else {
+ // }
+ // ...(..., ldsfld(field), ...)
+
+ ILCondition c = block.Body[i] as ILCondition;
+ if (c == null || c.Condition == null && c.TrueBlock == null || c.FalseBlock == null)
+ return;
+ if (!(c.TrueBlock.Body.Count == 1 && c.FalseBlock.Body.Count == 0))
+ return;
+ if (!c.Condition.Match(ILCode.LogicNot))
+ return;
+ ILExpression condition = c.Condition.Arguments.Single() as ILExpression;
+ if (condition == null || condition.Code != ILCode.Ldsfld)
+ return;
+ FieldDefinition field = ((FieldReference)condition.Operand).ResolveWithinSameModule(); // field is defined in current assembly
+ if (field == null || !field.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
+ return;
+ ILExpression stsfld = c.TrueBlock.Body[0] as ILExpression;
+ if (!(stsfld != null && stsfld.Code == ILCode.Stsfld && ((FieldReference)stsfld.Operand).ResolveWithinSameModule() == field))
+ return;
+ ILExpression newObj = stsfld.Arguments[0];
+ if (!(newObj.Code == ILCode.Newobj && newObj.Arguments.Count == 2))
+ return;
+ if (newObj.Arguments[0].Code != ILCode.Ldnull)
+ return;
+ if (newObj.Arguments[1].Code != ILCode.Ldftn)
+ return;
+ MethodDefinition anonymousMethod = ((MethodReference)newObj.Arguments[1].Operand).ResolveWithinSameModule(); // method is defined in current assembly
+ if (!Ast.Transforms.DelegateConstruction.IsAnonymousMethod(context, anonymousMethod))
+ return;
+
+ ILNode followingNode = block.Body.ElementAtOrDefault(i + 1);
+ if (followingNode != null && followingNode.GetSelfAndChildrenRecursive<ILExpression>().Count(
+ e => e.Code == ILCode.Ldsfld && ((FieldReference)e.Operand).ResolveWithinSameModule() == field) == 1)
+ {
+ foreach (ILExpression parent in followingNode.GetSelfAndChildrenRecursive<ILExpression>()) {
+ for (int j = 0; j < parent.Arguments.Count; j++) {
+ if (parent.Arguments[j].Code == ILCode.Ldsfld && ((FieldReference)parent.Arguments[j].Operand).ResolveWithinSameModule() == field) {
+ parent.Arguments[j] = newObj;
+ block.Body.RemoveAt(i);
+ i -= new ILInlining(method).InlineInto(block.Body, i, aggressive: false);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ void CachedDelegateInitializationWithLocal(ILBlock block, ref int i)
+ {
+ // if (logicnot(ldloc(v))) {
+ // stloc(v, newobj(Action::.ctor, ldloc(displayClass), ldftn(method)))
+ // } else {
+ // }
+ // ...(..., ldloc(v), ...)
+
+ ILCondition c = block.Body[i] as ILCondition;
+ if (c == null || c.Condition == null && c.TrueBlock == null || c.FalseBlock == null)
+ return;
+ if (!(c.TrueBlock.Body.Count == 1 && c.FalseBlock.Body.Count == 0))
+ return;
+ if (!c.Condition.Match(ILCode.LogicNot))
+ return;
+ ILExpression condition = c.Condition.Arguments.Single() as ILExpression;
+ if (condition == null || condition.Code != ILCode.Ldloc)
+ return;
+ ILVariable v = (ILVariable)condition.Operand;
+ ILExpression stloc = c.TrueBlock.Body[0] as ILExpression;
+ if (!(stloc != null && stloc.Code == ILCode.Stloc && (ILVariable)stloc.Operand == v))
+ return;
+ ILExpression newObj = stloc.Arguments[0];
+ if (!(newObj.Code == ILCode.Newobj && newObj.Arguments.Count == 2))
+ return;
+ if (newObj.Arguments[0].Code != ILCode.Ldloc)
+ return;
+ if (newObj.Arguments[1].Code != ILCode.Ldftn)
+ return;
+ MethodDefinition anonymousMethod = ((MethodReference)newObj.Arguments[1].Operand).ResolveWithinSameModule(); // method is defined in current assembly
+ if (!Ast.Transforms.DelegateConstruction.IsAnonymousMethod(context, anonymousMethod))
+ return;
+
+ ILNode followingNode = block.Body.ElementAtOrDefault(i + 1);
+ if (followingNode != null && followingNode.GetSelfAndChildrenRecursive<ILExpression>().Count(
+ e => e.Code == ILCode.Ldloc && (ILVariable)e.Operand == v) == 1)
+ {
+ ILInlining inlining = new ILInlining(method);
+ if (!(inlining.numLdloc.GetOrDefault(v) == 2 && inlining.numStloc.GetOrDefault(v) == 2 && inlining.numLdloca.GetOrDefault(v) == 0))
+ return;
+
+ // Find the store instruction that initializes the local to null:
+ foreach (ILBlock storeBlock in method.GetSelfAndChildrenRecursive<ILBlock>()) {
+ for (int j = 0; j < storeBlock.Body.Count; j++) {
+ ILVariable storedVar;
+ ILExpression storedExpr;
+ if (storeBlock.Body[j].Match(ILCode.Stloc, out storedVar, out storedExpr) && storedVar == v && storedExpr.Match(ILCode.Ldnull)) {
+ // Remove the instruction
+ storeBlock.Body.RemoveAt(j);
+ if (storeBlock == block && j < i)
+ i--;
+ break;
+ }
+ }
+ }
+
+ block.Body[i] = stloc; // remove the 'if (v==null)'
+ inlining = new ILInlining(method);
+ inlining.InlineIfPossible(block.Body, ref i);
+ }
+ }
+ #endregion
+
+ #region MakeAssignmentExpression
+ bool MakeAssignmentExpression(List<ILNode> body, ILExpression expr, int pos)
+ {
+ // exprVar = ...
+ // stloc(v, exprVar)
+ // ->
+ // exprVar = stloc(v, ...))
+ ILVariable exprVar;
+ ILExpression initializer;
+ if (!(expr.Match(ILCode.Stloc, out exprVar, out initializer) && exprVar.IsGenerated))
+ return false;
+ ILExpression nextExpr = body.ElementAtOrDefault(pos + 1) as ILExpression;
+ ILVariable v;
+ ILExpression stLocArg;
+ if (nextExpr.Match(ILCode.Stloc, out v, out stLocArg) && stLocArg.MatchLdloc(exprVar)) {
+ ILExpression store2 = body.ElementAtOrDefault(pos + 2) as ILExpression;
+ if (StoreCanBeConvertedToAssignment(store2, exprVar)) {
+ // expr_44 = ...
+ // stloc(v1, expr_44)
+ // anystore(v2, expr_44)
+ // ->
+ // stloc(v1, anystore(v2, ...))
+ ILInlining inlining = new ILInlining(method);
+ if (inlining.numLdloc.GetOrDefault(exprVar) == 2 && inlining.numStloc.GetOrDefault(exprVar) == 1) {
+ body.RemoveAt(pos + 2); // remove store2
+ body.RemoveAt(pos); // remove expr = ...
+ nextExpr.Arguments[0] = store2;
+ store2.Arguments[store2.Arguments.Count - 1] = initializer;
+
+ inlining.InlineIfPossible(body, ref pos);
+
+ return true;
+ }
+ }
+
+ body.RemoveAt(pos + 1); // remove stloc
+ nextExpr.Arguments[0] = initializer;
+ ((ILExpression)body[pos]).Arguments[0] = nextExpr;
+ return true;
+ } else if ((nextExpr.Code == ILCode.Stsfld || nextExpr.Code == ILCode.CallSetter || nextExpr.Code == ILCode.CallvirtSetter) && nextExpr.Arguments.Count == 1) {
+ // exprVar = ...
+ // stsfld(fld, exprVar)
+ // ->
+ // exprVar = stsfld(fld, ...))
+ if (nextExpr.Arguments[0].MatchLdloc(exprVar)) {
+ body.RemoveAt(pos + 1); // remove stsfld
+ nextExpr.Arguments[0] = initializer;
+ ((ILExpression)body[pos]).Arguments[0] = nextExpr;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool StoreCanBeConvertedToAssignment(ILExpression store, ILVariable exprVar)
+ {
+ if (store == null)
+ return false;
+ switch (store.Code) {
+ case ILCode.Stloc:
+ case ILCode.Stfld:
+ case ILCode.Stsfld:
+ case ILCode.Stobj:
+ case ILCode.CallSetter:
+ case ILCode.CallvirtSetter:
+ break;
+ default:
+ if (!store.Code.IsStoreToArray())
+ return false;
+ break;
+ }
+ return store.Arguments.Last().Code == ILCode.Ldloc && store.Arguments.Last().Operand == exprVar;
+ }
+ #endregion
+
+ #region MakeCompoundAssignments
+ bool MakeCompoundAssignments(List<ILNode> body, ILExpression expr, int pos)
+ {
+ bool modified = false;
+ modified |= MakeCompoundAssignment(expr);
+ // Static fields and local variables are not handled here - those are expressions without side effects
+ // and get handled by ReplaceMethodCallsWithOperators
+ // (which does a reversible transform to the short operator form, as the introduction of checked/unchecked might have to revert to the long form).
+ foreach (ILExpression arg in expr.Arguments) {
+ modified |= MakeCompoundAssignments(null, arg, -1);
+ }
+ if (modified && body != null)
+ new ILInlining(method).InlineInto(body, pos, aggressive: false);
+ return modified;
+ }
+
+ bool MakeCompoundAssignment(ILExpression expr)
+ {
+ // stelem.any(T, ldloc(array), ldloc(pos), <OP>(ldelem.any(T, ldloc(array), ldloc(pos)), <RIGHT>))
+ // or
+ // stobj(T, ldloc(ptr), <OP>(ldobj(T, ldloc(ptr)), <RIGHT>))
+ ILCode expectedLdelemCode;
+ switch (expr.Code) {
+ case ILCode.Stelem_Any:
+ expectedLdelemCode = ILCode.Ldelem_Any;
+ break;
+ case ILCode.Stfld:
+ expectedLdelemCode = ILCode.Ldfld;
+ break;
+ case ILCode.Stobj:
+ expectedLdelemCode = ILCode.Ldobj;
+ break;
+ case ILCode.CallSetter:
+ expectedLdelemCode = ILCode.CallGetter;
+ break;
+ case ILCode.CallvirtSetter:
+ expectedLdelemCode = ILCode.CallvirtGetter;
+ break;
+ default:
+ return false;
+ }
+
+ // all arguments except the last (so either array+pos, or ptr):
+ bool hasGeneratedVar = false;
+ for (int i = 0; i < expr.Arguments.Count - 1; i++) {
+ ILVariable inputVar;
+ if (!expr.Arguments[i].Match(ILCode.Ldloc, out inputVar))
+ return false;
+ hasGeneratedVar |= inputVar.IsGenerated;
+ }
+ // At least one of the variables must be generated; otherwise we just keep the expanded form.
+ // We do this because we want compound assignments to be represented in ILAst only when strictly necessary;
+ // other compound assignments will be introduced by ReplaceMethodCallsWithOperator
+ // (which uses a reversible transformation, see ReplaceMethodCallsWithOperator.RestoreOriginalAssignOperatorAnnotation)
+ if (!hasGeneratedVar)
+ return false;
+
+ ILExpression op = expr.Arguments.Last();
+ // in case of compound assignments with a lifted operator the result is inside NullableOf and the operand is inside ValueOf
+ bool liftedOperator = false;
+ if (op.Code == ILCode.NullableOf) {
+ op = op.Arguments[0];
+ liftedOperator = true;
+ }
+ if (!CanBeRepresentedAsCompoundAssignment(op))
+ return false;
+
+ ILExpression ldelem = op.Arguments[0];
+ if (liftedOperator) {
+ if (ldelem.Code != ILCode.ValueOf)
+ return false;
+ ldelem = ldelem.Arguments[0];
+ }
+ if (ldelem.Code != expectedLdelemCode)
+ return false;
+ Debug.Assert(ldelem.Arguments.Count == expr.Arguments.Count - 1);
+ for (int i = 0; i < ldelem.Arguments.Count; i++) {
+ if (!ldelem.Arguments[i].MatchLdloc((ILVariable)expr.Arguments[i].Operand))
+ return false;
+ }
+ expr.Code = ILCode.CompoundAssignment;
+ expr.Operand = null;
+ expr.Arguments.RemoveRange(0, ldelem.Arguments.Count);
+ // result is "CompoundAssignment(<OP>(ldelem.any(...), <RIGHT>))"
+ return true;
+ }
+
+ static bool CanBeRepresentedAsCompoundAssignment(ILExpression expr)
+ {
+ switch (expr.Code) {
+ case ILCode.Add:
+ case ILCode.Add_Ovf:
+ case ILCode.Add_Ovf_Un:
+ case ILCode.Sub:
+ case ILCode.Sub_Ovf:
+ case ILCode.Sub_Ovf_Un:
+ case ILCode.Mul:
+ case ILCode.Mul_Ovf:
+ case ILCode.Mul_Ovf_Un:
+ case ILCode.Div:
+ case ILCode.Div_Un:
+ case ILCode.Rem:
+ case ILCode.Rem_Un:
+ case ILCode.And:
+ case ILCode.Or:
+ case ILCode.Xor:
+ case ILCode.Shl:
+ case ILCode.Shr:
+ case ILCode.Shr_Un:
+ return true;
+ case ILCode.Call:
+ var m = expr.Operand as MethodReference;
+ if (m == null || m.HasThis || expr.Arguments.Count != 2) return false;
+ switch (m.Name) {
+ case "op_Addition":
+ case "op_Subtraction":
+ case "op_Multiply":
+ case "op_Division":
+ case "op_Modulus":
+ case "op_BitwiseAnd":
+ case "op_BitwiseOr":
+ case "op_ExclusiveOr":
+ case "op_LeftShift":
+ case "op_RightShift":
+ return true;
+ default:
+ return false;
+ }
+ default:
+ return false;
+ }
+ }
+ #endregion
+
+ #region IntroducePostIncrement
+
+ bool IntroducePostIncrement(List<ILNode> body, ILExpression expr, int pos)
+ {
+ bool modified = IntroducePostIncrementForVariables(body, expr, pos);
+ Debug.Assert(body[pos] == expr); // IntroducePostIncrementForVariables shouldn't change the expression reference
+ ILExpression newExpr = IntroducePostIncrementForInstanceFields(expr);
+ if (newExpr != null) {
+ modified = true;
+ body[pos] = newExpr;
+ new ILInlining(method).InlineIfPossible(body, ref pos);
+ }
+ return modified;
+ }
+
+ bool IntroducePostIncrementForVariables(List<ILNode> body, ILExpression expr, int pos)
+ {
+ // Works for variables and static fields/properties
+
+ // expr = ldloc(i)
+ // stloc(i, add(expr, ldc.i4(1)))
+ // ->
+ // expr = postincrement(1, ldloca(i))
+ ILVariable exprVar;
+ ILExpression exprInit;
+ if (!(expr.Match(ILCode.Stloc, out exprVar, out exprInit) && exprVar.IsGenerated))
+ return false;
+
+ //The next expression
+ ILExpression nextExpr = body.ElementAtOrDefault(pos + 1) as ILExpression;
+ if (nextExpr == null)
+ return false;
+
+ ILCode loadInstruction = exprInit.Code;
+ ILCode storeInstruction = nextExpr.Code;
+ bool recombineVariable = false;
+
+ // We only recognise local variables, static fields, and static getters with no arguments
+ switch (loadInstruction) {
+ case ILCode.Ldloc:
+ //Must be a matching store type
+ if (storeInstruction != ILCode.Stloc)
+ return false;
+ ILVariable loadVar = (ILVariable)exprInit.Operand;
+ ILVariable storeVar = (ILVariable)nextExpr.Operand;
+ if (loadVar != storeVar) {
+ if (loadVar.OriginalVariable != null && loadVar.OriginalVariable == storeVar.OriginalVariable)
+ recombineVariable = true;
+ else
+ return false;
+ }
+ break;
+ case ILCode.Ldsfld:
+ if (storeInstruction != ILCode.Stsfld)
+ return false;
+ if (exprInit.Operand != nextExpr.Operand)
+ return false;
+ break;
+ case ILCode.CallGetter:
+ // non-static getters would have the 'this' argument
+ if (exprInit.Arguments.Count != 0)
+ return false;
+ if (storeInstruction != ILCode.CallSetter)
+ return false;
+ if (!IsGetterSetterPair(exprInit.Operand, nextExpr.Operand))
+ return false;
+ break;
+ default:
+ return false;
+ }
+
+ ILExpression addExpr = nextExpr.Arguments[0];
+
+ int incrementAmount;
+ ILCode incrementCode = GetIncrementCode(addExpr, out incrementAmount);
+ if (!(incrementAmount != 0 && addExpr.Arguments[0].MatchLdloc(exprVar)))
+ return false;
+
+ if (recombineVariable) {
+ // Split local variable, unsplit these two instances
+ // replace nextExpr.Operand with exprInit.Operand
+ ReplaceVariables(method, oldVar => oldVar == nextExpr.Operand ? (ILVariable)exprInit.Operand : oldVar);
+ }
+
+ switch (loadInstruction) {
+ case ILCode.Ldloc:
+ exprInit.Code = ILCode.Ldloca;
+ break;
+ case ILCode.Ldsfld:
+ exprInit.Code = ILCode.Ldsflda;
+ break;
+ case ILCode.CallGetter:
+ exprInit = new ILExpression(ILCode.AddressOf, null, exprInit);
+ break;
+ }
+ expr.Arguments[0] = new ILExpression(incrementCode, incrementAmount, exprInit);
+ body.RemoveAt(pos + 1); // TODO ILRanges
+ return true;
+ }
+
+ static bool IsGetterSetterPair(object getterOperand, object setterOperand)
+ {
+ MethodReference getter = getterOperand as MethodReference;
+ MethodReference setter = setterOperand as MethodReference;
+ if (getter == null || setter == null)
+ return false;
+ if (!TypeAnalysis.IsSameType(getter.DeclaringType, setter.DeclaringType))
+ return false;
+ MethodDefinition getterDef = getter.Resolve();
+ MethodDefinition setterDef = setter.Resolve();
+ if (getterDef == null || setterDef == null)
+ return false;
+ foreach (PropertyDefinition prop in getterDef.DeclaringType.Properties) {
+ if (prop.GetMethod == getterDef)
+ return prop.SetMethod == setterDef;
+ }
+ return false;
+ }
+
+ ILExpression IntroducePostIncrementForInstanceFields(ILExpression expr)
+ {
+ // stfld(field, ldloc(instance), add(stloc(helperVar, ldfld(field, ldloc(instance))), ldc.i4(1)))
+ // -> stloc(helperVar, postincrement(1, ldflda(field, ldloc(instance))))
+
+ // Also works for array elements and pointers:
+
+ // stelem.any(T, ldloc(instance), ldloc(pos), add(stloc(helperVar, ldelem.any(T, ldloc(instance), ldloc(pos))), ldc.i4(1)))
+ // -> stloc(helperVar, postincrement(1, ldelema(ldloc(instance), ldloc(pos))))
+
+ // stobj(T, ldloc(ptr), add(stloc(helperVar, ldobj(T, ldloc(ptr)), ldc.i4(1))))
+ // -> stloc(helperVar, postIncrement(1, ldloc(ptr)))
+
+ // callsetter(set_P, ldloc(instance), add(stloc(helperVar, callgetter(get_P, ldloc(instance))), ldc.i4(1)))
+ // -> stloc(helperVar, postIncrement(1, propertyaddress. callgetter(get_P, ldloc(instance))))
+
+ if (!(expr.Code == ILCode.Stfld || expr.Code.IsStoreToArray() || expr.Code == ILCode.Stobj || expr.Code == ILCode.CallSetter || expr.Code == ILCode.CallvirtSetter))
+ return null;
+
+ // Test that all arguments except the last are ldloc (1 arg for fields and pointers, 2 args for arrays)
+ for (int i = 0; i < expr.Arguments.Count - 1; i++) {
+ if (expr.Arguments[i].Code != ILCode.Ldloc)
+ return null;
+ }
+
+ ILExpression addExpr = expr.Arguments[expr.Arguments.Count - 1];
+ int incrementAmount;
+ ILCode incrementCode = GetIncrementCode(addExpr, out incrementAmount);
+ ILVariable helperVar;
+ ILExpression initialValue;
+ if (!(incrementAmount != 0 && addExpr.Arguments[0].Match(ILCode.Stloc, out helperVar, out initialValue)))
+ return null;
+
+ if (expr.Code == ILCode.Stfld) {
+ if (initialValue.Code != ILCode.Ldfld)
+ return null;
+ // There might be two different FieldReference instances, so we compare the field's signatures:
+ FieldReference getField = (FieldReference)initialValue.Operand;
+ FieldReference setField = (FieldReference)expr.Operand;
+ if (!(TypeAnalysis.IsSameType(getField.DeclaringType, setField.DeclaringType)
+ && getField.Name == setField.Name && TypeAnalysis.IsSameType(getField.FieldType, setField.FieldType)))
+ {
+ return null;
+ }
+ } else if (expr.Code == ILCode.Stobj) {
+ if (!(initialValue.Code == ILCode.Ldobj && initialValue.Operand == expr.Operand))
+ return null;
+ } else if (expr.Code == ILCode.CallSetter) {
+ if (!(initialValue.Code == ILCode.CallGetter && IsGetterSetterPair(initialValue.Operand, expr.Operand)))
+ return null;
+ } else if (expr.Code == ILCode.CallvirtSetter) {
+ if (!(initialValue.Code == ILCode.CallvirtGetter && IsGetterSetterPair(initialValue.Operand, expr.Operand)))
+ return null;
+ } else {
+ if (!initialValue.Code.IsLoadFromArray())
+ return null;
+ }
+ Debug.Assert(expr.Arguments.Count - 1 == initialValue.Arguments.Count);
+ for (int i = 0; i < initialValue.Arguments.Count; i++) {
+ if (!initialValue.Arguments[i].MatchLdloc((ILVariable)expr.Arguments[i].Operand))
+ return null;
+ }
+
+ ILExpression stloc = addExpr.Arguments[0];
+ if (expr.Code == ILCode.Stobj) {
+ stloc.Arguments[0] = new ILExpression(ILCode.PostIncrement, incrementAmount, initialValue.Arguments[0]);
+ } else if (expr.Code == ILCode.CallSetter || expr.Code == ILCode.CallvirtSetter) {
+ initialValue = new ILExpression(ILCode.AddressOf, null, initialValue);
+ stloc.Arguments[0] = new ILExpression(ILCode.PostIncrement, incrementAmount, initialValue);
+ } else {
+ stloc.Arguments[0] = new ILExpression(ILCode.PostIncrement, incrementAmount, initialValue);
+ initialValue.Code = (expr.Code == ILCode.Stfld ? ILCode.Ldflda : ILCode.Ldelema);
+ }
+ // TODO: ILRanges?
+
+ return stloc;
+ }
+
+ ILCode GetIncrementCode(ILExpression addExpr, out int incrementAmount)
+ {
+ ILCode incrementCode;
+ bool decrement = false;
+ switch (addExpr.Code) {
+ case ILCode.Add:
+ incrementCode = ILCode.PostIncrement;
+ break;
+ case ILCode.Add_Ovf:
+ incrementCode = ILCode.PostIncrement_Ovf;
+ break;
+ case ILCode.Add_Ovf_Un:
+ incrementCode = ILCode.PostIncrement_Ovf_Un;
+ break;
+ case ILCode.Sub:
+ incrementCode = ILCode.PostIncrement;
+ decrement = true;
+ break;
+ case ILCode.Sub_Ovf:
+ incrementCode = ILCode.PostIncrement_Ovf;
+ decrement = true;
+ break;
+ case ILCode.Sub_Ovf_Un:
+ incrementCode = ILCode.PostIncrement_Ovf_Un;
+ decrement = true;
+ break;
+ default:
+ incrementAmount = 0;
+ return ILCode.Nop;
+ }
+ if (addExpr.Arguments[1].Match(ILCode.Ldc_I4, out incrementAmount)) {
+ if (incrementAmount == -1 || incrementAmount == 1) { // TODO pointer increment?
+ if (decrement)
+ incrementAmount = -incrementAmount;
+ return incrementCode;
+ }
+ }
+ incrementAmount = 0;
+ return ILCode.Nop;
+ }
+ #endregion
+
+ #region IntroduceFixedStatements
+ bool IntroduceFixedStatements(List<ILNode> body, int i)
+ {
+ ILExpression initValue;
+ ILVariable pinnedVar;
+ int initEndPos;
+ if (!MatchFixedInitializer(body, i, out pinnedVar, out initValue, out initEndPos))
+ return false;
+
+ ILFixedStatement fixedStmt = body.ElementAtOrDefault(initEndPos) as ILFixedStatement;
+ if (fixedStmt != null) {
+ ILExpression expr = fixedStmt.BodyBlock.Body.LastOrDefault() as ILExpression;
+ if (expr != null && expr.Code == ILCode.Stloc && expr.Operand == pinnedVar && IsNullOrZero(expr.Arguments[0])) {
+ // we found a second initializer for the existing fixed statement
+ fixedStmt.Initializers.Insert(0, initValue);
+ body.RemoveRange(i, initEndPos - i);
+ fixedStmt.BodyBlock.Body.RemoveAt(fixedStmt.BodyBlock.Body.Count - 1);
+ if (pinnedVar.Type.IsByReference)
+ pinnedVar.Type = new PointerType(((ByReferenceType)pinnedVar.Type).ElementType);
+ return true;
+ }
+ }
+
+ // find where pinnedVar is reset to 0:
+ int j;
+ for (j = initEndPos; j < body.Count; j++) {
+ ILVariable v2;
+ ILExpression storedVal;
+ // stloc(pinned_Var, conv.u(ldc.i4(0)))
+ if (body[j].Match(ILCode.Stloc, out v2, out storedVal) && v2 == pinnedVar) {
+ if (IsNullOrZero(storedVal)) {
+ break;
+ }
+ }
+ }
+ // Create fixed statement from i to j
+ fixedStmt = new ILFixedStatement();
+ fixedStmt.Initializers.Add(initValue);
+ fixedStmt.BodyBlock = new ILBlock(body.GetRange(initEndPos, j - initEndPos)); // from initEndPos to j-1 (inclusive)
+ body.RemoveRange(i + 1, Math.Min(j, body.Count - 1) - i); // from i+1 to j (inclusive)
+ body[i] = fixedStmt;
+ if (pinnedVar.Type.IsByReference)
+ pinnedVar.Type = new PointerType(((ByReferenceType)pinnedVar.Type).ElementType);
+
+ return true;
+ }
+
+ bool IsNullOrZero(ILExpression expr)
+ {
+ if (expr.Code == ILCode.Conv_U || expr.Code == ILCode.Conv_I)
+ expr = expr.Arguments[0];
+ return (expr.Code == ILCode.Ldc_I4 && (int)expr.Operand == 0) || expr.Code == ILCode.Ldnull;
+ }
+
+ bool MatchFixedInitializer(List<ILNode> body, int i, out ILVariable pinnedVar, out ILExpression initValue, out int nextPos)
+ {
+ if (body[i].Match(ILCode.Stloc, out pinnedVar, out initValue) && pinnedVar.IsPinned && !IsNullOrZero(initValue)) {
+ initValue = (ILExpression)body[i];
+ nextPos = i + 1;
+ HandleStringFixing(pinnedVar, body, ref nextPos, ref initValue);
+ return true;
+ }
+ ILCondition ifStmt = body[i] as ILCondition;
+ ILExpression arrayLoadingExpr;
+ if (ifStmt != null && MatchFixedArrayInitializerCondition(ifStmt.Condition, out arrayLoadingExpr)) {
+ ILVariable arrayVariable = (ILVariable)arrayLoadingExpr.Operand;
+ ILExpression trueValue;
+ if (ifStmt.TrueBlock != null && ifStmt.TrueBlock.Body.Count == 1
+ && ifStmt.TrueBlock.Body[0].Match(ILCode.Stloc, out pinnedVar, out trueValue)
+ && pinnedVar.IsPinned && IsNullOrZero(trueValue))
+ {
+ if (ifStmt.FalseBlock != null && ifStmt.FalseBlock.Body.Count == 1 && ifStmt.FalseBlock.Body[0] is ILFixedStatement) {
+ ILFixedStatement fixedStmt = (ILFixedStatement)ifStmt.FalseBlock.Body[0];
+ ILVariable stlocVar;
+ ILExpression falseValue;
+ if (fixedStmt.Initializers.Count == 1 && fixedStmt.BodyBlock.Body.Count == 0
+ && fixedStmt.Initializers[0].Match(ILCode.Stloc, out stlocVar, out falseValue) && stlocVar == pinnedVar)
+ {
+ ILVariable loadedVariable;
+ if (falseValue.Code == ILCode.Ldelema
+ && falseValue.Arguments[0].Match(ILCode.Ldloc, out loadedVariable) && loadedVariable == arrayVariable
+ && IsNullOrZero(falseValue.Arguments[1]))
+ {
+ // OK, we detected the pattern for fixing an array.
+ // Now check whether the loading expression was a store ot a temp. var
+ // that can be eliminated.
+ if (arrayLoadingExpr.Code == ILCode.Stloc) {
+ ILInlining inlining = new ILInlining(method);
+ if (inlining.numLdloc.GetOrDefault(arrayVariable) == 2 &&
+ inlining.numStloc.GetOrDefault(arrayVariable) == 1 && inlining.numLdloca.GetOrDefault(arrayVariable) == 0)
+ {
+ arrayLoadingExpr = arrayLoadingExpr.Arguments[0];
+ }
+ }
+ initValue = new ILExpression(ILCode.Stloc, pinnedVar, arrayLoadingExpr);
+ nextPos = i + 1;
+ return true;
+ }
+ }
+ }
+ }
+ }
+ initValue = null;
+ nextPos = -1;
+ return false;
+ }
+
+ bool MatchFixedArrayInitializerCondition(ILExpression condition, out ILExpression initValue)
+ {
+ ILExpression logicAnd;
+ ILVariable arrayVar;
+ if (condition.Match(ILCode.LogicNot, out logicAnd) && logicAnd.Code == ILCode.LogicAnd) {
+ initValue = UnpackDoubleNegation(logicAnd.Arguments[0]);
+ ILExpression arrayVarInitializer;
+ if (initValue.Match(ILCode.Ldloc, out arrayVar)
+ || initValue.Match(ILCode.Stloc, out arrayVar, out arrayVarInitializer))
+ {
+ ILExpression arrayLength = logicAnd.Arguments[1];
+ if (arrayLength.Code == ILCode.Conv_I4)
+ arrayLength = arrayLength.Arguments[0];
+ return arrayLength.Code == ILCode.Ldlen && arrayLength.Arguments[0].MatchLdloc(arrayVar);
+ }
+ }
+ initValue = null;
+ return false;
+ }
+
+ ILExpression UnpackDoubleNegation(ILExpression expr)
+ {
+ ILExpression negated;
+ if (expr.Match(ILCode.LogicNot, out negated) && negated.Match(ILCode.LogicNot, out negated))
+ return negated;
+ else
+ return expr;
+ }
+
+ bool HandleStringFixing(ILVariable pinnedVar, List<ILNode> body, ref int pos, ref ILExpression fixedStmtInitializer)
+ {
+ // fixed (stloc(pinnedVar, ldloc(text))) {
+ // var1 = var2 = conv.i(ldloc(pinnedVar))
+ // if (logicnot(logicnot(var1))) {
+ // var2 = add(var1, call(RuntimeHelpers::get_OffsetToStringData))
+ // }
+ // stloc(ptrVar, var2)
+ // ...
+
+ if (pos >= body.Count)
+ return false;
+
+ ILVariable var1, var2;
+ ILExpression varAssignment, ptrInitialization;
+ if (!(body[pos].Match(ILCode.Stloc, out var1, out varAssignment) && varAssignment.Match(ILCode.Stloc, out var2, out ptrInitialization)))
+ return false;
+ if (!(var1.IsGenerated && var2.IsGenerated))
+ return false;
+ if (ptrInitialization.Code == ILCode.Conv_I || ptrInitialization.Code == ILCode.Conv_U)
+ ptrInitialization = ptrInitialization.Arguments[0];
+ if (!ptrInitialization.MatchLdloc(pinnedVar))
+ return false;
+
+ ILCondition ifStmt = body[pos + 1] as ILCondition;
+ if (!(ifStmt != null && ifStmt.TrueBlock != null && ifStmt.TrueBlock.Body.Count == 1 && (ifStmt.FalseBlock == null || ifStmt.FalseBlock.Body.Count == 0)))
+ return false;
+ if (!UnpackDoubleNegation(ifStmt.Condition).MatchLdloc(var1))
+ return false;
+ ILVariable assignedVar;
+ ILExpression assignedExpr;
+ if (!(ifStmt.TrueBlock.Body[0].Match(ILCode.Stloc, out assignedVar, out assignedExpr) && assignedVar == var2 && assignedExpr.Code == ILCode.Add))
+ return false;
+ MethodReference calledMethod;
+ if (!(assignedExpr.Arguments[0].MatchLdloc(var1)))
+ return false;
+ if (!(assignedExpr.Arguments[1].Match(ILCode.Call, out calledMethod) || assignedExpr.Arguments[1].Match(ILCode.CallGetter, out calledMethod)))
+ return false;
+ if (!(calledMethod.Name == "get_OffsetToStringData" && calledMethod.DeclaringType.FullName == "System.Runtime.CompilerServices.RuntimeHelpers"))
+ return false;
+
+ ILVariable pointerVar;
+ if (body[pos + 2].Match(ILCode.Stloc, out pointerVar, out assignedExpr) && assignedExpr.MatchLdloc(var2)) {
+ pos += 3;
+ fixedStmtInitializer.Operand = pointerVar;
+ return true;
+ }
+ return false;
+ }
+ #endregion
+
+ #region SimplifyLogicNot
+ static bool SimplifyLogicNot(List<ILNode> body, ILExpression expr, int pos)
+ {
+ bool modified = false;
+ expr = SimplifyLogicNot(expr, ref modified);
+ Debug.Assert(expr == null);
+ return modified;
+ }
+
+ static ILExpression SimplifyLogicNot(ILExpression expr, ref bool modified)
+ {
+ ILExpression a;
+ // "ceq(a, ldc.i4.0)" becomes "logicnot(a)" if the inferred type for expression "a" is boolean
+ if (expr.Code == ILCode.Ceq && TypeAnalysis.IsBoolean(expr.Arguments[0].InferredType) && (a = expr.Arguments[1]).Code == ILCode.Ldc_I4 && (int)a.Operand == 0) {
+ expr.Code = ILCode.LogicNot;
+ expr.ILRanges.AddRange(a.ILRanges);
+ expr.Arguments.RemoveAt(1);
+ modified = true;
+ }
+
+ ILExpression res = null;
+ while (expr.Code == ILCode.LogicNot) {
+ a = expr.Arguments[0];
+ // remove double negation
+ if (a.Code == ILCode.LogicNot) {
+ res = a.Arguments[0];
+ res.ILRanges.AddRange(expr.ILRanges);
+ res.ILRanges.AddRange(a.ILRanges);
+ expr = res;
+ } else {
+ if (SimplifyLogicNotArgument(expr)) res = expr = a;
+ break;
+ }
+ }
+
+ for (int i = 0; i < expr.Arguments.Count; i++) {
+ a = SimplifyLogicNot(expr.Arguments[i], ref modified);
+ if (a != null) {
+ expr.Arguments[i] = a;
+ modified = true;
+ }
+ }
+
+ return res;
+ }
+
+ /// <summary>
+ /// If the argument is a binary comparison operation then the negation is pushed through it
+ /// </summary>
+ static bool SimplifyLogicNotArgument(ILExpression expr)
+ {
+ var a = expr.Arguments[0];
+ ILCode c;
+ switch (a.Code) {
+ case ILCode.Ceq: c = ILCode.Cne; break;
+ case ILCode.Cne: c = ILCode.Ceq; break;
+ case ILCode.Cgt: c = ILCode.Cle; break;
+ case ILCode.Cgt_Un: c = ILCode.Cle_Un; break;
+ case ILCode.Cge: c = ILCode.Clt; break;
+ case ILCode.Cge_Un: c = ILCode.Clt_Un; break;
+ case ILCode.Clt: c = ILCode.Cge; break;
+ case ILCode.Clt_Un: c = ILCode.Cge_Un; break;
+ case ILCode.Cle: c = ILCode.Cgt; break;
+ case ILCode.Cle_Un: c = ILCode.Cgt_Un; break;
+ default: return false;
+ }
+ a.Code = c;
+ a.ILRanges.AddRange(expr.ILRanges);
+ return true;
+ }
+ #endregion
+
+ #region SimplifyShiftOperators
+ static bool SimplifyShiftOperators(List<ILNode> body, ILExpression expr, int pos)
+ {
+ // C# compiles "a << b" to "a << (b & 31)", so we will remove the "& 31" if possible.
+ bool modified = false;
+ SimplifyShiftOperators(expr, ref modified);
+ return modified;
+ }
+
+ static void SimplifyShiftOperators(ILExpression expr, ref bool modified)
+ {
+ for (int i = 0; i < expr.Arguments.Count; i++)
+ SimplifyShiftOperators(expr.Arguments[i], ref modified);
+ if (expr.Code != ILCode.Shl && expr.Code != ILCode.Shr && expr.Code != ILCode.Shr_Un)
+ return;
+ var a = expr.Arguments[1];
+ if (a.Code != ILCode.And || a.Arguments[1].Code != ILCode.Ldc_I4 || expr.InferredType == null)
+ return;
+ int mask;
+ switch (expr.InferredType.MetadataType) {
+ case MetadataType.Int32:
+ case MetadataType.UInt32: mask = 31; break;
+ case MetadataType.Int64:
+ case MetadataType.UInt64: mask = 63; break;
+ default: return;
+ }
+ if ((int)a.Arguments[1].Operand != mask) return;
+ var res = a.Arguments[0];
+ res.ILRanges.AddRange(a.ILRanges);
+ res.ILRanges.AddRange(a.Arguments[1].ILRanges);
+ expr.Arguments[1] = res;
+ modified = true;
+ }
+ #endregion
+
+ #region InlineExpressionTreeParameterDeclarations
+ bool InlineExpressionTreeParameterDeclarations(List<ILNode> body, ILExpression expr, int pos)
+ {
+ // When there is a Expression.Lambda() call, and the parameters are declared in the
+ // IL statement immediately prior to the one containing the Lambda() call,
+ // using this code for the3 declaration:
+ // stloc(v, call(Expression::Parameter, call(Type::GetTypeFromHandle, ldtoken(...)), ldstr(...)))
+ // and the variables v are assigned only once (in that statements), and read only in a Expression::Lambda
+ // call that immediately follows the assignment statements, then we will inline those assignments
+ // into the Lambda call using ILCode.ExpressionTreeParameterDeclarations.
+
+ // This is sufficient to allow inlining over the expression tree construction. The remaining translation
+ // of expression trees into C# will be performed by a C# AST transformer.
+
+ for (int i = expr.Arguments.Count - 1; i >= 0; i--) {
+ if (InlineExpressionTreeParameterDeclarations(body, expr.Arguments[i], pos))
+ return true;
+ }
+
+ MethodReference mr;
+ ILExpression lambdaBodyExpr, parameterArray;
+ if (!(expr.Match(ILCode.Call, out mr, out lambdaBodyExpr, out parameterArray) && mr.Name == "Lambda"))
+ return false;
+ if (!(parameterArray.Code == ILCode.InitArray && mr.DeclaringType.FullName == "System.Linq.Expressions.Expression"))
+ return false;
+ int firstParameterPos = pos - parameterArray.Arguments.Count;
+ if (firstParameterPos < 0)
+ return false;
+
+ ILExpression[] parameterInitExpressions = new ILExpression[parameterArray.Arguments.Count + 1];
+ for (int i = 0; i < parameterArray.Arguments.Count; i++) {
+ parameterInitExpressions[i] = body[firstParameterPos + i] as ILExpression;
+ if (!MatchParameterVariableAssignment(parameterInitExpressions[i]))
+ return false;
+ ILVariable v = (ILVariable)parameterInitExpressions[i].Operand;
+ if (!parameterArray.Arguments[i].MatchLdloc(v))
+ return false;
+ // TODO: validate that the variable is only used here and within 'body'
+ }
+
+ parameterInitExpressions[parameterInitExpressions.Length - 1] = lambdaBodyExpr;
+ Debug.Assert(expr.Arguments[0] == lambdaBodyExpr);
+ expr.Arguments[0] = new ILExpression(ILCode.ExpressionTreeParameterDeclarations, null, parameterInitExpressions);
+
+ body.RemoveRange(firstParameterPos, parameterArray.Arguments.Count);
+
+ return true;
+ }
+
+ bool MatchParameterVariableAssignment(ILExpression expr)
+ {
+ // stloc(v, call(Expression::Parameter, call(Type::GetTypeFromHandle, ldtoken(...)), ldstr(...)))
+ ILVariable v;
+ ILExpression init;
+ if (!expr.Match(ILCode.Stloc, out v, out init))
+ return false;
+ if (v.IsGenerated || v.IsParameter || v.IsPinned)
+ return false;
+ if (v.Type == null || v.Type.FullName != "System.Linq.Expressions.ParameterExpression")
+ return false;
+ MethodReference parameterMethod;
+ ILExpression typeArg, nameArg;
+ if (!init.Match(ILCode.Call, out parameterMethod, out typeArg, out nameArg))
+ return false;
+ if (!(parameterMethod.Name == "Parameter" && parameterMethod.DeclaringType.FullName == "System.Linq.Expressions.Expression"))
+ return false;
+ MethodReference getTypeFromHandle;
+ ILExpression typeToken;
+ if (!typeArg.Match(ILCode.Call, out getTypeFromHandle, out typeToken))
+ return false;
+ if (!(getTypeFromHandle.Name == "GetTypeFromHandle" && getTypeFromHandle.DeclaringType.FullName == "System.Type"))
+ return false;
+ return typeToken.Code == ILCode.Ldtoken && nameArg.Code == ILCode.Ldstr;
+ }
+ #endregion
+ }
+}
diff --git a/ICSharpCode.Decompiler/ILAst/SimpleControlFlow.cs b/ICSharpCode.Decompiler/ILAst/SimpleControlFlow.cs
new file mode 100644
index 00000000..d3a74a37
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/SimpleControlFlow.cs
@@ -0,0 +1,376 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+using Mono.Cecil;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ public class SimpleControlFlow
+ {
+ Dictionary<ILLabel, int> labelGlobalRefCount = new Dictionary<ILLabel, int>();
+ Dictionary<ILLabel, ILBasicBlock> labelToBasicBlock = new Dictionary<ILLabel, ILBasicBlock>();
+
+ DecompilerContext context;
+ TypeSystem typeSystem;
+
+ public SimpleControlFlow(DecompilerContext context, ILBlock method)
+ {
+ this.context = context;
+ typeSystem = context.CurrentMethod.Module.TypeSystem;
+
+ foreach(ILLabel target in method.GetSelfAndChildrenRecursive<ILExpression>(e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())) {
+ labelGlobalRefCount[target] = labelGlobalRefCount.GetOrDefault(target) + 1;
+ }
+ foreach(ILBasicBlock bb in method.GetSelfAndChildrenRecursive<ILBasicBlock>()) {
+ foreach(ILLabel label in bb.GetChildren().OfType<ILLabel>()) {
+ labelToBasicBlock[label] = bb;
+ }
+ }
+ }
+
+ public bool SimplifyTernaryOperator(List<ILNode> body, ILBasicBlock head, int pos)
+ {
+ Debug.Assert(body.Contains(head));
+
+ ILExpression condExpr;
+ ILLabel trueLabel;
+ ILLabel falseLabel;
+ ILVariable trueLocVar = null;
+ ILExpression trueExpr;
+ ILLabel trueFall;
+ ILVariable falseLocVar = null;
+ ILExpression falseExpr;
+ ILLabel falseFall;
+ object unused;
+
+ if (head.MatchLastAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel) &&
+ labelGlobalRefCount[trueLabel] == 1 &&
+ labelGlobalRefCount[falseLabel] == 1 &&
+ ((labelToBasicBlock[trueLabel].MatchSingleAndBr(ILCode.Stloc, out trueLocVar, out trueExpr, out trueFall) &&
+ labelToBasicBlock[falseLabel].MatchSingleAndBr(ILCode.Stloc, out falseLocVar, out falseExpr, out falseFall) &&
+ trueLocVar == falseLocVar && trueFall == falseFall) ||
+ (labelToBasicBlock[trueLabel].MatchSingle(ILCode.Ret, out unused, out trueExpr) &&
+ labelToBasicBlock[falseLabel].MatchSingle(ILCode.Ret, out unused, out falseExpr))) &&
+ body.Contains(labelToBasicBlock[trueLabel]) &&
+ body.Contains(labelToBasicBlock[falseLabel])
+ )
+ {
+ bool isStloc = trueLocVar != null;
+ ILCode opCode = isStloc ? ILCode.Stloc : ILCode.Ret;
+ TypeReference retType = isStloc ? trueLocVar.Type : context.CurrentMethod.ReturnType;
+ bool retTypeIsBoolean = TypeAnalysis.IsBoolean(retType);
+ int leftBoolVal;
+ int rightBoolVal;
+ ILExpression newExpr;
+ // a ? true:false is equivalent to a
+ // a ? false:true is equivalent to !a
+ // a ? true : b is equivalent to a || b
+ // a ? b : true is equivalent to !a || b
+ // a ? b : false is equivalent to a && b
+ // a ? false : b is equivalent to !a && b
+ if (retTypeIsBoolean &&
+ trueExpr.Match(ILCode.Ldc_I4, out leftBoolVal) &&
+ falseExpr.Match(ILCode.Ldc_I4, out rightBoolVal) &&
+ ((leftBoolVal != 0 && rightBoolVal == 0) || (leftBoolVal == 0 && rightBoolVal != 0))
+ )
+ {
+ // It can be expressed as trivilal expression
+ if (leftBoolVal != 0) {
+ newExpr = condExpr;
+ } else {
+ newExpr = new ILExpression(ILCode.LogicNot, null, condExpr) { InferredType = typeSystem.Boolean };
+ }
+ } else if ((retTypeIsBoolean || TypeAnalysis.IsBoolean(falseExpr.InferredType)) && trueExpr.Match(ILCode.Ldc_I4, out leftBoolVal) && (leftBoolVal == 0 || leftBoolVal == 1)) {
+ // It can be expressed as logical expression
+ if (leftBoolVal != 0) {
+ newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicOr, condExpr, falseExpr);
+ } else {
+ newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicAnd, new ILExpression(ILCode.LogicNot, null, condExpr), falseExpr);
+ }
+ } else if ((retTypeIsBoolean || TypeAnalysis.IsBoolean(trueExpr.InferredType)) && falseExpr.Match(ILCode.Ldc_I4, out rightBoolVal) && (rightBoolVal == 0 || rightBoolVal == 1)) {
+ // It can be expressed as logical expression
+ if (rightBoolVal != 0) {
+ newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicOr, new ILExpression(ILCode.LogicNot, null, condExpr), trueExpr);
+ } else {
+ newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicAnd, condExpr, trueExpr);
+ }
+ } else {
+ // Ternary operator tends to create long complicated return statements
+ if (opCode == ILCode.Ret)
+ return false;
+
+ // Only simplify generated variables
+ if (opCode == ILCode.Stloc && !trueLocVar.IsGenerated)
+ return false;
+
+ // Create ternary expression
+ newExpr = new ILExpression(ILCode.TernaryOp, null, condExpr, trueExpr, falseExpr);
+ }
+
+ head.Body.RemoveTail(ILCode.Brtrue, ILCode.Br);
+ head.Body.Add(new ILExpression(opCode, trueLocVar, newExpr));
+ if (isStloc)
+ head.Body.Add(new ILExpression(ILCode.Br, trueFall));
+
+ // Remove the old basic blocks
+ body.RemoveOrThrow(labelToBasicBlock[trueLabel]);
+ body.RemoveOrThrow(labelToBasicBlock[falseLabel]);
+
+ return true;
+ }
+ return false;
+ }
+
+ public bool SimplifyNullCoalescing(List<ILNode> body, ILBasicBlock head, int pos)
+ {
+ // ...
+ // v = ldloc(leftVar)
+ // brtrue(endBBLabel, ldloc(leftVar))
+ // br(rightBBLabel)
+ //
+ // rightBBLabel:
+ // v = rightExpr
+ // br(endBBLabel)
+ // ...
+ // =>
+ // ...
+ // v = NullCoalescing(ldloc(leftVar), rightExpr)
+ // br(endBBLabel)
+
+ ILVariable v, v2;
+ ILExpression leftExpr, leftExpr2;
+ ILVariable leftVar;
+ ILLabel endBBLabel, endBBLabel2;
+ ILLabel rightBBLabel;
+ ILBasicBlock rightBB;
+ ILExpression rightExpr;
+ if (head.Body.Count >= 3 &&
+ head.Body[head.Body.Count - 3].Match(ILCode.Stloc, out v, out leftExpr) &&
+ leftExpr.Match(ILCode.Ldloc, out leftVar) &&
+ head.MatchLastAndBr(ILCode.Brtrue, out endBBLabel, out leftExpr2, out rightBBLabel) &&
+ leftExpr2.MatchLdloc(leftVar) &&
+ labelToBasicBlock.TryGetValue(rightBBLabel, out rightBB) &&
+ rightBB.MatchSingleAndBr(ILCode.Stloc, out v2, out rightExpr, out endBBLabel2) &&
+ v == v2 &&
+ endBBLabel == endBBLabel2 &&
+ labelGlobalRefCount.GetOrDefault(rightBBLabel) == 1 &&
+ body.Contains(rightBB)
+ )
+ {
+ head.Body.RemoveTail(ILCode.Stloc, ILCode.Brtrue, ILCode.Br);
+ head.Body.Add(new ILExpression(ILCode.Stloc, v, new ILExpression(ILCode.NullCoalescing, null, leftExpr, rightExpr)));
+ head.Body.Add(new ILExpression(ILCode.Br, endBBLabel));
+
+ body.RemoveOrThrow(labelToBasicBlock[rightBBLabel]);
+ return true;
+ }
+ return false;
+ }
+
+ public bool SimplifyShortCircuit(List<ILNode> body, ILBasicBlock head, int pos)
+ {
+ Debug.Assert(body.Contains(head));
+
+ ILExpression condExpr;
+ ILLabel trueLabel;
+ ILLabel falseLabel;
+ if(head.MatchLastAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel)) {
+ for (int pass = 0; pass < 2; pass++) {
+
+ // On the second pass, swap labels and negate expression of the first branch
+ // It is slightly ugly, but much better then copy-pasting this whole block
+ ILLabel nextLabel = (pass == 0) ? trueLabel : falseLabel;
+ ILLabel otherLablel = (pass == 0) ? falseLabel : trueLabel;
+ bool negate = (pass == 1);
+
+ ILBasicBlock nextBasicBlock = labelToBasicBlock[nextLabel];
+ ILExpression nextCondExpr;
+ ILLabel nextTrueLablel;
+ ILLabel nextFalseLabel;
+ if (body.Contains(nextBasicBlock) &&
+ nextBasicBlock != head &&
+ labelGlobalRefCount[(ILLabel)nextBasicBlock.Body.First()] == 1 &&
+ nextBasicBlock.MatchSingleAndBr(ILCode.Brtrue, out nextTrueLablel, out nextCondExpr, out nextFalseLabel) &&
+ (otherLablel == nextFalseLabel || otherLablel == nextTrueLablel))
+ {
+ // Create short cicuit branch
+ ILExpression logicExpr;
+ if (otherLablel == nextFalseLabel) {
+ logicExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicAnd, negate ? new ILExpression(ILCode.LogicNot, null, condExpr) : condExpr, nextCondExpr);
+ } else {
+ logicExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicOr, negate ? condExpr : new ILExpression(ILCode.LogicNot, null, condExpr), nextCondExpr);
+ }
+ head.Body.RemoveTail(ILCode.Brtrue, ILCode.Br);
+ head.Body.Add(new ILExpression(ILCode.Brtrue, nextTrueLablel, logicExpr));
+ head.Body.Add(new ILExpression(ILCode.Br, nextFalseLabel));
+
+ // Remove the inlined branch from scope
+ body.RemoveOrThrow(nextBasicBlock);
+
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public bool SimplifyCustomShortCircuit(List<ILNode> body, ILBasicBlock head, int pos)
+ {
+ Debug.Assert(body.Contains(head));
+
+ // --- looking for the following pattern ---
+ // stloc(targetVar, leftVar)
+ // brtrue(exitLabel, call(op_False, leftVar)
+ // br(followingBlock)
+ //
+ // FollowingBlock:
+ // stloc(targetVar, call(op_BitwiseAnd, leftVar, rightExpression))
+ // br(exitLabel)
+ // ---
+
+ if (head.Body.Count < 3)
+ return false;
+
+ // looking for:
+ // stloc(targetVar, leftVar)
+ ILVariable targetVar;
+ ILExpression targetVarInitExpr;
+ if (!head.Body[head.Body.Count - 3].Match(ILCode.Stloc, out targetVar, out targetVarInitExpr))
+ return false;
+
+ ILVariable leftVar;
+ if (!targetVarInitExpr.Match(ILCode.Ldloc, out leftVar))
+ return false;
+
+ // looking for:
+ // brtrue(exitLabel, call(op_False, leftVar)
+ // br(followingBlock)
+ ILExpression callExpr;
+ ILLabel exitLabel;
+ ILLabel followingBlock;
+ if(!head.MatchLastAndBr(ILCode.Brtrue, out exitLabel, out callExpr, out followingBlock))
+ return false;
+
+ if (labelGlobalRefCount[followingBlock] > 1)
+ return false;
+
+ MethodReference opFalse;
+ ILExpression opFalseArg;
+ if (!callExpr.Match(ILCode.Call, out opFalse, out opFalseArg))
+ return false;
+
+ // ignore operators other than op_False and op_True
+ if (opFalse.Name != "op_False" && opFalse.Name != "op_True")
+ return false;
+
+ if (!opFalseArg.MatchLdloc(leftVar))
+ return false;
+
+ ILBasicBlock followingBasicBlock = labelToBasicBlock[followingBlock];
+
+ // FollowingBlock:
+ // stloc(targetVar, call(op_BitwiseAnd, leftVar, rightExpression))
+ // br(exitLabel)
+ ILVariable _targetVar;
+ ILExpression opBitwiseCallExpr;
+ ILLabel _exitLabel;
+ if (!followingBasicBlock.MatchSingleAndBr(ILCode.Stloc, out _targetVar, out opBitwiseCallExpr, out _exitLabel))
+ return false;
+
+ if (_targetVar != targetVar || exitLabel != _exitLabel)
+ return false;
+
+ MethodReference opBitwise;
+ ILExpression leftVarExpression;
+ ILExpression rightExpression;
+ if (!opBitwiseCallExpr.Match(ILCode.Call, out opBitwise, out leftVarExpression, out rightExpression))
+ return false;
+
+ if (!leftVarExpression.MatchLdloc(leftVar))
+ return false;
+
+ // ignore operators other than op_BitwiseAnd and op_BitwiseOr
+ if (opBitwise.Name != "op_BitwiseAnd" && opBitwise.Name != "op_BitwiseOr")
+ return false;
+
+ // insert:
+ // stloc(targetVar, LogicAnd(C::op_BitwiseAnd, leftVar, rightExpression)
+ // br(exitLabel)
+ ILCode op = opBitwise.Name == "op_BitwiseAnd" ? ILCode.LogicAnd : ILCode.LogicOr;
+
+ if (op == ILCode.LogicAnd && opFalse.Name != "op_False")
+ return false;
+
+ if (op == ILCode.LogicOr && opFalse.Name != "op_True")
+ return false;
+
+ ILExpression shortCircuitExpr = MakeLeftAssociativeShortCircuit(op, opFalseArg, rightExpression);
+ shortCircuitExpr.Operand = opBitwise;
+
+ head.Body.RemoveTail(ILCode.Stloc, ILCode.Brtrue, ILCode.Br);
+ head.Body.Add(new ILExpression(ILCode.Stloc, targetVar, shortCircuitExpr));
+ head.Body.Add(new ILExpression(ILCode.Br, exitLabel));
+ body.Remove(followingBasicBlock);
+
+ return true;
+ }
+
+ ILExpression MakeLeftAssociativeShortCircuit(ILCode code, ILExpression left, ILExpression right)
+ {
+ // Assuming that the inputs are already left associative
+ if (right.Match(code)) {
+ // Find the leftmost logical expression
+ ILExpression current = right;
+ while(current.Arguments[0].Match(code))
+ current = current.Arguments[0];
+ current.Arguments[0] = new ILExpression(code, null, left, current.Arguments[0]) { InferredType = typeSystem.Boolean };
+ return right;
+ } else {
+ return new ILExpression(code, null, left, right) { InferredType = typeSystem.Boolean };
+ }
+ }
+
+ public bool JoinBasicBlocks(List<ILNode> body, ILBasicBlock head, int pos)
+ {
+ ILLabel nextLabel;
+ ILBasicBlock nextBB;
+ if (!head.Body.ElementAtOrDefault(head.Body.Count - 2).IsConditionalControlFlow() &&
+ head.Body.Last().Match(ILCode.Br, out nextLabel) &&
+ labelGlobalRefCount[nextLabel] == 1 &&
+ labelToBasicBlock.TryGetValue(nextLabel, out nextBB) &&
+ body.Contains(nextBB) &&
+ nextBB.Body.First() == nextLabel &&
+ !nextBB.Body.OfType<ILTryCatchBlock>().Any()
+ )
+ {
+ head.Body.RemoveTail(ILCode.Br);
+ nextBB.Body.RemoveAt(0); // Remove label
+ head.Body.AddRange(nextBB.Body);
+
+ body.RemoveOrThrow(nextBB);
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/ILAst/StateRange.cs b/ICSharpCode.Decompiler/ILAst/StateRange.cs
new file mode 100644
index 00000000..ef27c498
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/StateRange.cs
@@ -0,0 +1,312 @@
+// Copyright (c) 2012 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Mono.Cecil;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ internal struct Interval
+ {
+ public readonly int Start, End;
+
+ public Interval(int start, int end)
+ {
+ Debug.Assert(start <= end || (start == 0 && end == -1));
+ Start = start;
+ End = end;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("({0} to {1})", Start, End);
+ }
+ }
+
+ internal class StateRange
+ {
+ readonly List<Interval> data = new List<Interval>();
+
+ public StateRange()
+ {
+ }
+
+ public StateRange(int start, int end)
+ {
+ data.Add(new Interval(start, end));
+ }
+
+ public bool IsEmpty {
+ get { return data.Count == 0; }
+ }
+
+ public bool Contains(int val)
+ {
+ foreach (Interval v in data) {
+ if (v.Start <= val && val <= v.End)
+ return true;
+ }
+ return false;
+ }
+
+ public void UnionWith(StateRange other)
+ {
+ data.AddRange(other.data);
+ }
+
+ /// <summary>
+ /// Unions this state range with (other intersect (minVal to maxVal))
+ /// </summary>
+ public void UnionWith(StateRange other, int minVal, int maxVal)
+ {
+ foreach (Interval v in other.data) {
+ int start = Math.Max(v.Start, minVal);
+ int end = Math.Min(v.End, maxVal);
+ if (start <= end)
+ data.Add(new Interval(start, end));
+ }
+ }
+
+ /// <summary>
+ /// Merges overlapping interval ranges.
+ /// </summary>
+ public void Simplify()
+ {
+ if (data.Count < 2)
+ return;
+ data.Sort((a, b) => a.Start.CompareTo(b.Start));
+ Interval prev = data[0];
+ int prevIndex = 0;
+ for (int i = 1; i < data.Count; i++) {
+ Interval next = data[i];
+ Debug.Assert(prev.Start <= next.Start);
+ if (next.Start <= prev.End + 1) { // intervals overlapping or touching
+ prev = new Interval(prev.Start, Math.Max(prev.End, next.End));
+ data[prevIndex] = prev;
+ data[i] = new Interval(0, -1); // mark as deleted
+ } else {
+ prev = next;
+ prevIndex = i;
+ }
+ }
+ data.RemoveAll(i => i.Start > i.End); // remove all entries that were marked as deleted
+ }
+
+ public override string ToString()
+ {
+ return string.Join(",", data);
+ }
+
+ public Interval ToEnclosingInterval()
+ {
+ if (data.Count == 0)
+ throw new SymbolicAnalysisFailedException();
+ return new Interval(data[0].Start, data[data.Count - 1].End);
+ }
+ }
+
+ internal enum StateRangeAnalysisMode
+ {
+ IteratorMoveNext,
+ IteratorDispose,
+ AsyncMoveNext
+ }
+
+ internal class StateRangeAnalysis
+ {
+ readonly StateRangeAnalysisMode mode;
+ readonly FieldDefinition stateField;
+ internal DefaultDictionary<ILNode, StateRange> ranges;
+ SymbolicEvaluationContext evalContext;
+
+ internal Dictionary<MethodDefinition, StateRange> finallyMethodToStateRange; // used only for IteratorDispose
+
+ /// <summary>
+ /// Initializes the state range logic:
+ /// Clears 'ranges' and sets 'ranges[entryPoint]' to the full range (int.MinValue to int.MaxValue)
+ /// </summary>
+ public StateRangeAnalysis(ILNode entryPoint, StateRangeAnalysisMode mode, FieldDefinition stateField, ILVariable cachedStateVar = null)
+ {
+ this.mode = mode;
+ this.stateField = stateField;
+ if (mode == StateRangeAnalysisMode.IteratorDispose) {
+ finallyMethodToStateRange = new Dictionary<MethodDefinition, StateRange>();
+ }
+
+ ranges = new DefaultDictionary<ILNode, StateRange>(n => new StateRange());
+ ranges[entryPoint] = new StateRange(int.MinValue, int.MaxValue);
+ evalContext = new SymbolicEvaluationContext(stateField);
+ if (cachedStateVar != null)
+ evalContext.AddStateVariable(cachedStateVar);
+ }
+
+ public int AssignStateRanges(List<ILNode> body, int bodyLength)
+ {
+ if (bodyLength == 0)
+ return 0;
+ for (int i = 0; i < bodyLength; i++) {
+ StateRange nodeRange = ranges[body[i]];
+ nodeRange.Simplify();
+
+ ILLabel label = body[i] as ILLabel;
+ if (label != null) {
+ ranges[body[i + 1]].UnionWith(nodeRange);
+ continue;
+ }
+
+ ILTryCatchBlock tryFinally = body[i] as ILTryCatchBlock;
+ if (tryFinally != null) {
+ if (mode == StateRangeAnalysisMode.IteratorDispose) {
+ if (tryFinally.CatchBlocks.Count != 0 || tryFinally.FaultBlock != null || tryFinally.FinallyBlock == null)
+ throw new SymbolicAnalysisFailedException();
+ ranges[tryFinally.TryBlock].UnionWith(nodeRange);
+ if (tryFinally.TryBlock.Body.Count != 0) {
+ ranges[tryFinally.TryBlock.Body[0]].UnionWith(nodeRange);
+ AssignStateRanges(tryFinally.TryBlock.Body, tryFinally.TryBlock.Body.Count);
+ }
+ continue;
+ } else if (mode == StateRangeAnalysisMode.AsyncMoveNext) {
+ return i;
+ } else {
+ throw new SymbolicAnalysisFailedException();
+ }
+ }
+
+ ILExpression expr = body[i] as ILExpression;
+ if (expr == null)
+ throw new SymbolicAnalysisFailedException();
+ switch (expr.Code) {
+ case ILCode.Switch:
+ {
+ SymbolicValue val = evalContext.Eval(expr.Arguments[0]);
+ if (val.Type != SymbolicValueType.State)
+ goto default;
+ ILLabel[] targetLabels = (ILLabel[])expr.Operand;
+ for (int j = 0; j < targetLabels.Length; j++) {
+ int state = j - val.Constant;
+ ranges[targetLabels[j]].UnionWith(nodeRange, state, state);
+ }
+ StateRange nextRange = ranges[body[i + 1]];
+ nextRange.UnionWith(nodeRange, int.MinValue, -1 - val.Constant);
+ nextRange.UnionWith(nodeRange, targetLabels.Length - val.Constant, int.MaxValue);
+ break;
+ }
+ case ILCode.Br:
+ case ILCode.Leave:
+ ranges[(ILLabel)expr.Operand].UnionWith(nodeRange);
+ break;
+ case ILCode.Brtrue:
+ {
+ SymbolicValue val = evalContext.Eval(expr.Arguments[0]).AsBool();
+ if (val.Type == SymbolicValueType.StateEquals) {
+ ranges[(ILLabel)expr.Operand].UnionWith(nodeRange, val.Constant, val.Constant);
+ StateRange nextRange = ranges[body[i + 1]];
+ nextRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1);
+ nextRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue);
+ break;
+ } else if (val.Type == SymbolicValueType.StateInEquals) {
+ ranges[body[i + 1]].UnionWith(nodeRange, val.Constant, val.Constant);
+ StateRange targetRange = ranges[(ILLabel)expr.Operand];
+ targetRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1);
+ targetRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue);
+ break;
+ } else {
+ goto default;
+ }
+ }
+ case ILCode.Nop:
+ ranges[body[i + 1]].UnionWith(nodeRange);
+ break;
+ case ILCode.Ret:
+ break;
+ case ILCode.Stloc:
+ {
+ SymbolicValue val = evalContext.Eval(expr.Arguments[0]);
+ if (val.Type == SymbolicValueType.State && val.Constant == 0) {
+ evalContext.AddStateVariable((ILVariable)expr.Operand);
+ goto case ILCode.Nop;
+ } else {
+ goto default;
+ }
+ }
+ case ILCode.Call:
+ // in some cases (e.g. foreach over array) the C# compiler produces a finally method outside of try-finally blocks
+ if (mode == StateRangeAnalysisMode.IteratorDispose) {
+ MethodDefinition mdef = (expr.Operand as MethodReference).ResolveWithinSameModule();
+ if (mdef == null || finallyMethodToStateRange.ContainsKey(mdef))
+ throw new SymbolicAnalysisFailedException();
+ finallyMethodToStateRange.Add(mdef, nodeRange);
+ break;
+ } else {
+ goto default;
+ }
+ default:
+ if (mode == StateRangeAnalysisMode.IteratorDispose) {
+ throw new SymbolicAnalysisFailedException();
+ } else {
+ return i;
+ }
+ }
+ }
+ return bodyLength;
+ }
+
+ public void EnsureLabelAtPos(List<ILNode> body, ref int pos, ref int bodyLength)
+ {
+ if (pos > 0 && body[pos - 1] is ILLabel) {
+ pos--;
+ } else {
+ // ensure that the first element at body[pos] is a label:
+ ILLabel newLabel = new ILLabel();
+ newLabel.Name = "YieldReturnEntryPoint";
+ ranges[newLabel] = ranges[body[pos]]; // give the label the range of the instruction at body[pos]
+ body.Insert(pos, newLabel);
+ bodyLength++;
+ }
+ }
+
+ public LabelRangeMapping CreateLabelRangeMapping(List<ILNode> body, int pos, int bodyLength)
+ {
+ LabelRangeMapping result = new LabelRangeMapping();
+ CreateLabelRangeMapping(body, pos, bodyLength, result, false);
+ return result;
+ }
+
+ void CreateLabelRangeMapping(List<ILNode> body, int pos, int bodyLength, LabelRangeMapping result, bool onlyInitialLabels)
+ {
+ for (int i = pos; i < bodyLength; i++) {
+ ILLabel label = body[i] as ILLabel;
+ if (label != null) {
+ result.Add(new KeyValuePair<ILLabel, StateRange>(label, ranges[label]));
+ } else {
+ ILTryCatchBlock tryCatchBlock = body[i] as ILTryCatchBlock;
+ if (tryCatchBlock != null) {
+ CreateLabelRangeMapping(tryCatchBlock.TryBlock.Body, 0, tryCatchBlock.TryBlock.Body.Count, result, true);
+ } else if (onlyInitialLabels) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ internal class LabelRangeMapping : List<KeyValuePair<ILLabel, StateRange>> {}
+}
diff --git a/ICSharpCode.Decompiler/ILAst/SymbolicExecution.cs b/ICSharpCode.Decompiler/ILAst/SymbolicExecution.cs
new file mode 100644
index 00000000..76c57eb0
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/SymbolicExecution.cs
@@ -0,0 +1,157 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Mono.Cecil;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ /// <summary>
+ /// This exception is thrown when we find something else than we expect from the C# compiler.
+ /// This aborts the analysis and makes the whole transform fail.
+ /// </summary>
+ internal class SymbolicAnalysisFailedException : Exception {}
+
+ internal enum SymbolicValueType
+ {
+ /// <summary>
+ /// Unknown value
+ /// </summary>
+ Unknown,
+ /// <summary>
+ /// int: Constant (result of ldc.i4)
+ /// </summary>
+ IntegerConstant,
+ /// <summary>
+ /// int: State + Constant
+ /// </summary>
+ State,
+ /// <summary>
+ /// This pointer (result of ldarg.0)
+ /// </summary>
+ This,
+ /// <summary>
+ /// bool: State == Constant
+ /// </summary>
+ StateEquals,
+ /// <summary>
+ /// bool: State != Constant
+ /// </summary>
+ StateInEquals
+ }
+
+ internal struct SymbolicValue
+ {
+ public readonly int Constant;
+ public readonly SymbolicValueType Type;
+
+ public SymbolicValue(SymbolicValueType type, int constant = 0)
+ {
+ Type = type;
+ Constant = constant;
+ }
+
+ public SymbolicValue AsBool()
+ {
+ if (Type == SymbolicValueType.State) {
+ // convert state integer to bool:
+ // if (state + c) = if (state + c != 0) = if (state != -c)
+ return new SymbolicValue(SymbolicValueType.StateInEquals, unchecked(-Constant));
+ }
+ return this;
+ }
+ public override string ToString()
+ {
+ return string.Format("[SymbolicValue {0}: {1}]", Type, Constant);
+ }
+ }
+
+ internal class SymbolicEvaluationContext
+ {
+ readonly FieldDefinition stateField;
+ readonly List<ILVariable> stateVariables = new List<ILVariable>();
+
+ public SymbolicEvaluationContext(FieldDefinition stateField)
+ {
+ this.stateField = stateField;
+ }
+
+ public void AddStateVariable(ILVariable v)
+ {
+ if (!stateVariables.Contains(v))
+ stateVariables.Add(v);
+ }
+
+ SymbolicValue Failed()
+ {
+ return new SymbolicValue(SymbolicValueType.Unknown);
+ }
+
+ public SymbolicValue Eval(ILExpression expr)
+ {
+ SymbolicValue left, right;
+ switch (expr.Code) {
+ case ILCode.Sub:
+ left = Eval(expr.Arguments[0]);
+ right = Eval(expr.Arguments[1]);
+ if (left.Type != SymbolicValueType.State && left.Type != SymbolicValueType.IntegerConstant)
+ return Failed();
+ if (right.Type != SymbolicValueType.IntegerConstant)
+ return Failed();
+ return new SymbolicValue(left.Type, unchecked ( left.Constant - right.Constant ));
+ case ILCode.Ldfld:
+ if (Eval(expr.Arguments[0]).Type != SymbolicValueType.This)
+ return Failed();
+ if (CecilExtensions.ResolveWithinSameModule(expr.Operand as FieldReference) != stateField)
+ return Failed();
+ return new SymbolicValue(SymbolicValueType.State);
+ case ILCode.Ldloc:
+ ILVariable loadedVariable = (ILVariable)expr.Operand;
+ if (stateVariables.Contains(loadedVariable))
+ return new SymbolicValue(SymbolicValueType.State);
+ else if (loadedVariable.IsParameter && loadedVariable.OriginalParameter.Index < 0)
+ return new SymbolicValue(SymbolicValueType.This);
+ else
+ return Failed();
+ case ILCode.Ldc_I4:
+ return new SymbolicValue(SymbolicValueType.IntegerConstant, (int)expr.Operand);
+ case ILCode.Ceq:
+ case ILCode.Cne:
+ left = Eval(expr.Arguments[0]);
+ right = Eval(expr.Arguments[1]);
+ if (left.Type != SymbolicValueType.State || right.Type != SymbolicValueType.IntegerConstant)
+ return Failed();
+ // bool: (state + left.Constant == right.Constant)
+ // bool: (state == right.Constant - left.Constant)
+ return new SymbolicValue(expr.Code == ILCode.Ceq ? SymbolicValueType.StateEquals : SymbolicValueType.StateInEquals, unchecked(right.Constant - left.Constant));
+ case ILCode.LogicNot:
+ SymbolicValue val = Eval(expr.Arguments[0]).AsBool();
+ if (val.Type == SymbolicValueType.StateEquals)
+ return new SymbolicValue(SymbolicValueType.StateInEquals, val.Constant);
+ else if (val.Type == SymbolicValueType.StateInEquals)
+ return new SymbolicValue(SymbolicValueType.StateEquals, val.Constant);
+ else
+ return Failed();
+ default:
+ return Failed();
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs
new file mode 100644
index 00000000..1931d390
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs
@@ -0,0 +1,1294 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ /// <summary>
+ /// Assigns C# types to IL expressions.
+ /// </summary>
+ /// <remarks>
+ /// Types are inferred in a bidirectional manner:
+ /// The expected type flows from the outside to the inside, the actual inferred type flows from the inside to the outside.
+ /// </remarks>
+ public class TypeAnalysis
+ {
+ public static void Run(DecompilerContext context, ILBlock method)
+ {
+ TypeAnalysis ta = new TypeAnalysis();
+ ta.context = context;
+ ta.module = context.CurrentMethod.Module;
+ ta.typeSystem = ta.module.TypeSystem;
+ ta.method = method;
+ ta.CreateDependencyGraph(method);
+ ta.IdentifySingleLoadVariables();
+ ta.RunInference();
+ }
+
+ sealed class ExpressionToInfer
+ {
+ public ILExpression Expression;
+
+ public bool Done;
+
+ /// <summary>
+ /// Set for assignment expressions that should wait until the variable type is available
+ /// from the context where the variable is used.
+ /// </summary>
+ public ILVariable DependsOnSingleLoad;
+
+ /// <summary>
+ /// The list variables that are read by this expression.
+ /// </summary>
+ public List<ILVariable> Dependencies = new List<ILVariable>();
+
+ public override string ToString()
+ {
+ if (Done)
+ return "[Done] " + Expression.ToString();
+ else
+ return Expression.ToString();
+ }
+
+ }
+
+ DecompilerContext context;
+ TypeSystem typeSystem;
+ ILBlock method;
+ ModuleDefinition module;
+ List<ExpressionToInfer> allExpressions = new List<ExpressionToInfer>();
+ DefaultDictionary<ILVariable, List<ExpressionToInfer>> assignmentExpressions = new DefaultDictionary<ILVariable, List<ExpressionToInfer>>(_ => new List<ExpressionToInfer>());
+ HashSet<ILVariable> singleLoadVariables = new HashSet<ILVariable>();
+
+ #region CreateDependencyGraph
+ /// <summary>
+ /// Creates the "ExpressionToInfer" instances (=nodes in dependency graph)
+ /// </summary>
+ /// <remarks>
+ /// We are using a dependency graph to ensure that expressions are analyzed in the correct order.
+ /// </remarks>
+ void CreateDependencyGraph(ILNode node)
+ {
+ ILCondition cond = node as ILCondition;
+ if (cond != null) {
+ cond.Condition.ExpectedType = typeSystem.Boolean;
+ }
+ ILWhileLoop loop = node as ILWhileLoop;
+ if (loop != null && loop.Condition != null) {
+ loop.Condition.ExpectedType = typeSystem.Boolean;
+ }
+ ILTryCatchBlock.CatchBlock catchBlock = node as ILTryCatchBlock.CatchBlock;
+ if (catchBlock != null && catchBlock.ExceptionVariable != null && catchBlock.ExceptionType != null && catchBlock.ExceptionVariable.Type == null) {
+ catchBlock.ExceptionVariable.Type = catchBlock.ExceptionType;
+ }
+ ILExpression expr = node as ILExpression;
+ if (expr != null) {
+ ExpressionToInfer expressionToInfer = new ExpressionToInfer();
+ expressionToInfer.Expression = expr;
+ allExpressions.Add(expressionToInfer);
+ FindNestedAssignments(expr, expressionToInfer);
+
+ if (expr.Code == ILCode.Stloc && ((ILVariable)expr.Operand).Type == null)
+ assignmentExpressions[(ILVariable)expr.Operand].Add(expressionToInfer);
+ return;
+ }
+ foreach (ILNode child in node.GetChildren()) {
+ CreateDependencyGraph(child);
+ }
+ }
+
+ void FindNestedAssignments(ILExpression expr, ExpressionToInfer parent)
+ {
+ foreach (ILExpression arg in expr.Arguments) {
+ if (arg.Code == ILCode.Stloc) {
+ ExpressionToInfer expressionToInfer = new ExpressionToInfer();
+ expressionToInfer.Expression = arg;
+ allExpressions.Add(expressionToInfer);
+ FindNestedAssignments(arg, expressionToInfer);
+ ILVariable v = (ILVariable)arg.Operand;
+ if (v.Type == null) {
+ assignmentExpressions[v].Add(expressionToInfer);
+ // the instruction that consumes the stloc result is handled as if it was reading the variable
+ parent.Dependencies.Add(v);
+ }
+ } else {
+ ILVariable v;
+ if (arg.Match(ILCode.Ldloc, out v) && v.Type == null) {
+ parent.Dependencies.Add(v);
+ }
+ FindNestedAssignments(arg, parent);
+ }
+ }
+ }
+ #endregion
+
+ void IdentifySingleLoadVariables()
+ {
+ // Find all variables that are assigned to exactly a single time:
+ var q = from expr in allExpressions
+ from v in expr.Dependencies
+ group expr by v;
+ foreach (var g in q.ToArray()) {
+ ILVariable v = g.Key;
+ if (g.Count() == 1 && g.Single().Expression.GetSelfAndChildrenRecursive<ILExpression>().Count(e => e.Operand == v) == 1) {
+ singleLoadVariables.Add(v);
+ // Mark the assignments as dependent on the type from the single load:
+ foreach (var assignment in assignmentExpressions[v]) {
+ assignment.DependsOnSingleLoad = v;
+ }
+ }
+ }
+ }
+
+ void RunInference()
+ {
+ int numberOfExpressionsAlreadyInferred = 0;
+ // Two flags that allow resolving cycles:
+ bool ignoreSingleLoadDependencies = false;
+ bool assignVariableTypesBasedOnPartialInformation = false;
+ while (numberOfExpressionsAlreadyInferred < allExpressions.Count) {
+ int oldCount = numberOfExpressionsAlreadyInferred;
+ foreach (ExpressionToInfer expr in allExpressions) {
+ if (!expr.Done && expr.Dependencies.TrueForAll(v => v.Type != null || singleLoadVariables.Contains(v))
+ && (expr.DependsOnSingleLoad == null || expr.DependsOnSingleLoad.Type != null || ignoreSingleLoadDependencies))
+ {
+ RunInference(expr.Expression);
+ expr.Done = true;
+ numberOfExpressionsAlreadyInferred++;
+ }
+ }
+ if (numberOfExpressionsAlreadyInferred == oldCount) {
+ if (ignoreSingleLoadDependencies) {
+ if (assignVariableTypesBasedOnPartialInformation)
+ throw new InvalidOperationException("Could not infer any expression");
+ else
+ assignVariableTypesBasedOnPartialInformation = true;
+ } else {
+ // We have a cyclic dependency; we'll try if we can resolve it by ignoring single-load dependencies.
+ // This can happen if the variable was not actually assigned an expected type by the single-load instruction.
+ ignoreSingleLoadDependencies = true;
+ continue;
+ }
+ } else {
+ assignVariableTypesBasedOnPartialInformation = false;
+ ignoreSingleLoadDependencies = false;
+ }
+ // Now infer types for variables:
+ foreach (var pair in assignmentExpressions) {
+ ILVariable v = pair.Key;
+ if (v.Type == null && (assignVariableTypesBasedOnPartialInformation ? pair.Value.Any(e => e.Done) : pair.Value.All(e => e.Done))) {
+ TypeReference inferredType = null;
+ foreach (ExpressionToInfer expr in pair.Value) {
+ Debug.Assert(expr.Expression.Code == ILCode.Stloc);
+ ILExpression assignedValue = expr.Expression.Arguments.Single();
+ if (assignedValue.InferredType != null) {
+ if (inferredType == null) {
+ inferredType = assignedValue.InferredType;
+ } else {
+ // pick the common base type
+ inferredType = TypeWithMoreInformation(inferredType, assignedValue.InferredType);
+ }
+ }
+ }
+ if (inferredType == null)
+ inferredType = typeSystem.Object;
+ v.Type = inferredType;
+ // Assign inferred type to all the assignments (in case they used different inferred types):
+ foreach (ExpressionToInfer expr in pair.Value) {
+ expr.Expression.InferredType = inferredType;
+ // re-infer if the expected type has changed
+ InferTypeForExpression(expr.Expression.Arguments.Single(), inferredType);
+ }
+ }
+ }
+ }
+ }
+
+ void RunInference(ILExpression expr)
+ {
+ bool anyArgumentIsMissingExpectedType = expr.Arguments.Any(a => a.ExpectedType == null);
+ if (expr.InferredType == null || anyArgumentIsMissingExpectedType)
+ InferTypeForExpression(expr, expr.ExpectedType, forceInferChildren: anyArgumentIsMissingExpectedType);
+ foreach (var arg in expr.Arguments) {
+ if (arg.Code != ILCode.Stloc) {
+ RunInference(arg);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Infers the C# type of <paramref name="expr"/>.
+ /// </summary>
+ /// <param name="expr">The expression</param>
+ /// <param name="expectedType">The expected type of the expression</param>
+ /// <param name="forceInferChildren">Whether direct children should be inferred even if its not necessary. (does not apply to nested children!)</param>
+ /// <returns>The inferred type</returns>
+ TypeReference InferTypeForExpression(ILExpression expr, TypeReference expectedType, bool forceInferChildren = false)
+ {
+ if (expectedType != null && !IsSameType(expr.ExpectedType, expectedType)) {
+ expr.ExpectedType = expectedType;
+ if (expr.Code != ILCode.Stloc) // stloc is special case and never gets re-evaluated
+ forceInferChildren = true;
+ }
+ if (forceInferChildren || expr.InferredType == null)
+ expr.InferredType = DoInferTypeForExpression(expr, expectedType, forceInferChildren);
+ return expr.InferredType;
+ }
+
+ TypeReference DoInferTypeForExpression(ILExpression expr, TypeReference expectedType, bool forceInferChildren = false)
+ {
+ switch (expr.Code) {
+ #region Logical operators
+ case ILCode.LogicNot:
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments.Single(), typeSystem.Boolean);
+ }
+ return typeSystem.Boolean;
+ case ILCode.LogicAnd:
+ case ILCode.LogicOr:
+ // if Operand is set the logic and/or expression is a custom operator
+ // we can deal with it the same as a normal invocation.
+ if (expr.Operand != null)
+ goto case ILCode.Call;
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments[0], typeSystem.Boolean);
+ InferTypeForExpression(expr.Arguments[1], typeSystem.Boolean);
+ }
+ return typeSystem.Boolean;
+ case ILCode.TernaryOp:
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments[0], typeSystem.Boolean);
+ }
+ return InferBinaryArguments(expr.Arguments[1], expr.Arguments[2], expectedType, forceInferChildren);
+ case ILCode.NullCoalescing:
+ return InferBinaryArguments(expr.Arguments[0], expr.Arguments[1], expectedType, forceInferChildren);
+ #endregion
+ #region Variable load/store
+ case ILCode.Stloc:
+ {
+ ILVariable v = (ILVariable)expr.Operand;
+ if (forceInferChildren) {
+ // do not use 'expectedType' in here!
+ InferTypeForExpression(expr.Arguments.Single(), v.Type);
+ }
+ return v.Type;
+ }
+ case ILCode.Ldloc:
+ {
+ ILVariable v = (ILVariable)expr.Operand;
+ if (v.Type == null && singleLoadVariables.Contains(v)) {
+ v.Type = expectedType;
+ }
+ return v.Type;
+ }
+ case ILCode.Ldloca:
+ {
+ ILVariable v = (ILVariable)expr.Operand;
+ if (v.Type != null)
+ return new ByReferenceType(v.Type);
+ else
+ return null;
+ }
+ #endregion
+ #region Call / NewObj
+ case ILCode.Call:
+ case ILCode.Callvirt:
+ case ILCode.CallGetter:
+ case ILCode.CallvirtGetter:
+ case ILCode.CallSetter:
+ case ILCode.CallvirtSetter:
+ {
+ MethodReference method = (MethodReference)expr.Operand;
+ if (forceInferChildren) {
+ for (int i = 0; i < expr.Arguments.Count; i++) {
+ if (i == 0 && method.HasThis) {
+ InferTypeForExpression(expr.Arguments[0], MakeRefIfValueType(method.DeclaringType, expr.GetPrefix(ILCode.Constrained)));
+ } else {
+ InferTypeForExpression(expr.Arguments[i], SubstituteTypeArgs(method.Parameters[method.HasThis ? i - 1 : i].ParameterType, method));
+ }
+ }
+ }
+ if (expr.Code == ILCode.CallSetter || expr.Code == ILCode.CallvirtSetter) {
+ return SubstituteTypeArgs(method.Parameters.Last().ParameterType, method);
+ } else {
+ return SubstituteTypeArgs(method.ReturnType, method);
+ }
+ }
+ case ILCode.Newobj:
+ {
+ MethodReference ctor = (MethodReference)expr.Operand;
+ if (forceInferChildren) {
+ for (int i = 0; i < ctor.Parameters.Count; i++) {
+ InferTypeForExpression(expr.Arguments[i], SubstituteTypeArgs(ctor.Parameters[i].ParameterType, ctor));
+ }
+ }
+ return ctor.DeclaringType;
+ }
+ case ILCode.InitObject:
+ case ILCode.InitCollection:
+ return InferTypeForExpression(expr.Arguments[0], expectedType);
+ case ILCode.InitializedObject:
+ // expectedType should always be known due to the parent method call / property setter
+ Debug.Assert(expectedType != null);
+ return expectedType;
+ #endregion
+ #region Load/Store Fields
+ case ILCode.Ldfld:
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments[0], MakeRefIfValueType(((FieldReference)expr.Operand).DeclaringType, expr.GetPrefix(ILCode.Constrained)));
+ }
+ return GetFieldType((FieldReference)expr.Operand);
+ case ILCode.Ldsfld:
+ return GetFieldType((FieldReference)expr.Operand);
+ case ILCode.Ldflda:
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments[0], MakeRefIfValueType(((FieldReference)expr.Operand).DeclaringType, expr.GetPrefix(ILCode.Constrained)));
+ }
+ return new ByReferenceType(GetFieldType((FieldReference)expr.Operand));
+ case ILCode.Ldsflda:
+ return new ByReferenceType(GetFieldType((FieldReference)expr.Operand));
+ case ILCode.Stfld:
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments[0], MakeRefIfValueType(((FieldReference)expr.Operand).DeclaringType, expr.GetPrefix(ILCode.Constrained)));
+ InferTypeForExpression(expr.Arguments[1], GetFieldType((FieldReference)expr.Operand));
+ }
+ return GetFieldType((FieldReference)expr.Operand);
+ case ILCode.Stsfld:
+ if (forceInferChildren)
+ InferTypeForExpression(expr.Arguments[0], GetFieldType((FieldReference)expr.Operand));
+ return GetFieldType((FieldReference)expr.Operand);
+ #endregion
+ #region Reference/Pointer instructions
+ case ILCode.Ldind_Ref:
+ return UnpackPointer(InferTypeForExpression(expr.Arguments[0], null));
+ case ILCode.Stind_Ref:
+ if (forceInferChildren) {
+ TypeReference elementType = UnpackPointer(InferTypeForExpression(expr.Arguments[0], null));
+ InferTypeForExpression(expr.Arguments[1], elementType);
+ }
+ return null;
+ case ILCode.Ldobj:
+ {
+ TypeReference type = (TypeReference)expr.Operand;
+ var argType = InferTypeForExpression(expr.Arguments[0], null);
+ if (argType is PointerType || argType is ByReferenceType) {
+ var elementType = ((TypeSpecification)argType).ElementType;
+ int infoAmount = GetInformationAmount(elementType);
+ if (infoAmount == 1 && GetInformationAmount(type) == 8) {
+ // A bool can be loaded from both bytes and sbytes.
+ type = elementType;
+ }
+ if (infoAmount >= 8 && infoAmount <= 64 && infoAmount == GetInformationAmount(type)) {
+ // An integer can be loaded as another integer of the same size.
+ // For integers smaller than 32 bit, the signs must match (as loading performs sign extension)
+ bool? elementTypeIsSigned = IsSigned(elementType);
+ bool? typeIsSigned = IsSigned(type);
+ if (elementTypeIsSigned != null && typeIsSigned != null) {
+ if (infoAmount >= 32 || elementTypeIsSigned == typeIsSigned)
+ type = elementType;
+ }
+ }
+ }
+ if (argType is PointerType)
+ InferTypeForExpression(expr.Arguments[0], new PointerType(type));
+ else
+ InferTypeForExpression(expr.Arguments[0], new ByReferenceType(type));
+ return type;
+ }
+ case ILCode.Stobj:
+ {
+ TypeReference operandType = (TypeReference)expr.Operand;
+ TypeReference pointerType = InferTypeForExpression(expr.Arguments[0], new ByReferenceType(operandType));
+ TypeReference elementType;
+ if (pointerType is PointerType)
+ elementType = ((PointerType)pointerType).ElementType;
+ else if (pointerType is ByReferenceType)
+ elementType = ((ByReferenceType)pointerType).ElementType;
+ else
+ elementType = null;
+ if (elementType != null) {
+ // An integer can be stored in any other integer of the same size.
+ int infoAmount = GetInformationAmount(elementType);
+ if (infoAmount == 1 && GetInformationAmount(operandType) == 8)
+ operandType = elementType;
+ else if (infoAmount == GetInformationAmount(operandType) && IsSigned(elementType) != null && IsSigned(operandType) != null)
+ operandType = elementType;
+ }
+ if (forceInferChildren) {
+ if (pointerType is PointerType)
+ InferTypeForExpression(expr.Arguments[0], new PointerType(operandType));
+ else if (!IsSameType(operandType, expr.Operand as TypeReference))
+ InferTypeForExpression(expr.Arguments[0], new ByReferenceType(operandType));
+ InferTypeForExpression(expr.Arguments[1], operandType);
+ }
+ return operandType;
+ }
+ case ILCode.Initobj:
+ return null;
+ case ILCode.DefaultValue:
+ return (TypeReference)expr.Operand;
+ case ILCode.Localloc:
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments[0], null);
+ }
+ if (expectedType is PointerType)
+ return expectedType;
+ else
+ return typeSystem.IntPtr;
+ case ILCode.Sizeof:
+ return typeSystem.Int32;
+ case ILCode.PostIncrement:
+ case ILCode.PostIncrement_Ovf:
+ case ILCode.PostIncrement_Ovf_Un:
+ {
+ TypeReference elementType = UnpackPointer(InferTypeForExpression(expr.Arguments[0], null));
+ if (forceInferChildren && elementType != null) {
+ // Assign expected type to the child expression
+ InferTypeForExpression(expr.Arguments[0], new ByReferenceType(elementType));
+ }
+ return elementType;
+ }
+ case ILCode.Mkrefany:
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments[0], (TypeReference)expr.Operand);
+ }
+ return typeSystem.TypedReference;
+ case ILCode.Refanytype:
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments[0], typeSystem.TypedReference);
+ }
+ return new TypeReference("System", "RuntimeTypeHandle", module, module.TypeSystem.Corlib, true);
+ case ILCode.Refanyval:
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments[0], typeSystem.TypedReference);
+ }
+ return new ByReferenceType((TypeReference)expr.Operand);
+ case ILCode.AddressOf:
+ {
+ TypeReference t = InferTypeForExpression(expr.Arguments[0], UnpackPointer(expectedType));
+ return t != null ? new ByReferenceType(t) : null;
+ }
+ case ILCode.ValueOf:
+ return GetNullableTypeArgument(InferTypeForExpression(expr.Arguments[0], CreateNullableType(expectedType)));
+ case ILCode.NullableOf:
+ return CreateNullableType(InferTypeForExpression(expr.Arguments[0], GetNullableTypeArgument(expectedType)));
+ #endregion
+ #region Arithmetic instructions
+ case ILCode.Not: // bitwise complement
+ case ILCode.Neg:
+ return InferTypeForExpression(expr.Arguments.Single(), expectedType);
+ case ILCode.Add:
+ return InferArgumentsInAddition(expr, null, expectedType);
+ case ILCode.Sub:
+ return InferArgumentsInSubtraction(expr, null, expectedType);
+ case ILCode.Mul:
+ case ILCode.Or:
+ case ILCode.And:
+ case ILCode.Xor:
+ return InferArgumentsInBinaryOperator(expr, null, expectedType);
+ case ILCode.Add_Ovf:
+ return InferArgumentsInAddition(expr, true, expectedType);
+ case ILCode.Sub_Ovf:
+ return InferArgumentsInSubtraction(expr, true, expectedType);
+ case ILCode.Mul_Ovf:
+ case ILCode.Div:
+ case ILCode.Rem:
+ return InferArgumentsInBinaryOperator(expr, true, expectedType);
+ case ILCode.Add_Ovf_Un:
+ return InferArgumentsInAddition(expr, false, expectedType);
+ case ILCode.Sub_Ovf_Un:
+ return InferArgumentsInSubtraction(expr, false, expectedType);
+ case ILCode.Mul_Ovf_Un:
+ case ILCode.Div_Un:
+ case ILCode.Rem_Un:
+ return InferArgumentsInBinaryOperator(expr, false, expectedType);
+ case ILCode.Shl:
+ if (forceInferChildren)
+ InferTypeForExpression(expr.Arguments[1], typeSystem.Int32);
+ if (expectedType != null && (
+ expectedType.MetadataType == MetadataType.Int32 || expectedType.MetadataType == MetadataType.UInt32 ||
+ expectedType.MetadataType == MetadataType.Int64 || expectedType.MetadataType == MetadataType.UInt64)
+ )
+ return NumericPromotion(InferTypeForExpression(expr.Arguments[0], expectedType));
+ else
+ return NumericPromotion(InferTypeForExpression(expr.Arguments[0], null));
+ case ILCode.Shr:
+ case ILCode.Shr_Un:
+ {
+ if (forceInferChildren)
+ InferTypeForExpression(expr.Arguments[1], typeSystem.Int32);
+ TypeReference type = NumericPromotion(InferTypeForExpression(expr.Arguments[0], null));
+ if (type == null)
+ return null;
+ TypeReference expectedInputType = null;
+ switch (type.MetadataType) {
+ case MetadataType.Int32:
+ if (expr.Code == ILCode.Shr_Un)
+ expectedInputType = typeSystem.UInt32;
+ break;
+ case MetadataType.UInt32:
+ if (expr.Code == ILCode.Shr)
+ expectedInputType = typeSystem.Int32;
+ break;
+ case MetadataType.Int64:
+ if (expr.Code == ILCode.Shr_Un)
+ expectedInputType = typeSystem.UInt64;
+ break;
+ case MetadataType.UInt64:
+ if (expr.Code == ILCode.Shr)
+ expectedInputType = typeSystem.UInt64;
+ break;
+ }
+ if (expectedInputType != null) {
+ InferTypeForExpression(expr.Arguments[0], expectedInputType);
+ return expectedInputType;
+ } else {
+ return type;
+ }
+ }
+ case ILCode.CompoundAssignment:
+ {
+ var op = expr.Arguments[0];
+ if (op.Code == ILCode.NullableOf) op = op.Arguments[0].Arguments[0];
+ var varType = InferTypeForExpression(op.Arguments[0], null);
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments[0], varType);
+ }
+ return varType;
+ }
+ #endregion
+ #region Constant loading instructions
+ case ILCode.Ldnull:
+ return typeSystem.Object;
+ case ILCode.Ldstr:
+ return typeSystem.String;
+ case ILCode.Ldftn:
+ case ILCode.Ldvirtftn:
+ return typeSystem.IntPtr;
+ case ILCode.Ldc_I4:
+ if (IsBoolean(expectedType) && ((int)expr.Operand == 0 || (int)expr.Operand == 1))
+ return typeSystem.Boolean;
+ if (expectedType is PointerType && (int)expr.Operand == 0)
+ return expectedType;
+ if (IsIntegerOrEnum(expectedType) && OperandFitsInType(expectedType, (int)expr.Operand))
+ return expectedType;
+ else
+ return typeSystem.Int32;
+ case ILCode.Ldc_I8:
+ if (expectedType is PointerType && (long)expr.Operand == 0)
+ return expectedType;
+ if (IsIntegerOrEnum(expectedType) && GetInformationAmount(expectedType) >= NativeInt)
+ return expectedType;
+ else
+ return typeSystem.Int64;
+ case ILCode.Ldc_R4:
+ return typeSystem.Single;
+ case ILCode.Ldc_R8:
+ return typeSystem.Double;
+ case ILCode.Ldc_Decimal:
+ return new TypeReference("System", "Decimal", module, module.TypeSystem.Corlib, true);
+ case ILCode.Ldtoken:
+ if (expr.Operand is TypeReference)
+ return new TypeReference("System", "RuntimeTypeHandle", module, module.TypeSystem.Corlib, true);
+ else if (expr.Operand is FieldReference)
+ return new TypeReference("System", "RuntimeFieldHandle", module, module.TypeSystem.Corlib, true);
+ else
+ return new TypeReference("System", "RuntimeMethodHandle", module, module.TypeSystem.Corlib, true);
+ case ILCode.Arglist:
+ return new TypeReference("System", "RuntimeArgumentHandle", module, module.TypeSystem.Corlib, true);
+ #endregion
+ #region Array instructions
+ case ILCode.Newarr:
+ if (forceInferChildren) {
+ var lengthType = InferTypeForExpression(expr.Arguments.Single(), null);
+ if (lengthType == typeSystem.IntPtr) {
+ lengthType = typeSystem.Int64;
+ } else if (lengthType == typeSystem.UIntPtr) {
+ lengthType = typeSystem.UInt64;
+ } else if (lengthType != typeSystem.UInt32 && lengthType != typeSystem.Int64 && lengthType != typeSystem.UInt64) {
+ lengthType = typeSystem.Int32;
+ }
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments.Single(), lengthType);
+ }
+ }
+ return new ArrayType((TypeReference)expr.Operand);
+ case ILCode.InitArray:
+ var operandAsArrayType = (ArrayType)expr.Operand;
+ if (forceInferChildren)
+ {
+ foreach (ILExpression arg in expr.Arguments)
+ InferTypeForExpression(arg, operandAsArrayType.ElementType);
+ }
+ return operandAsArrayType;
+ case ILCode.Ldlen:
+ return typeSystem.Int32;
+ case ILCode.Ldelem_U1:
+ case ILCode.Ldelem_U2:
+ case ILCode.Ldelem_U4:
+ case ILCode.Ldelem_I1:
+ case ILCode.Ldelem_I2:
+ case ILCode.Ldelem_I4:
+ case ILCode.Ldelem_I8:
+ case ILCode.Ldelem_R4:
+ case ILCode.Ldelem_R8:
+ case ILCode.Ldelem_I:
+ case ILCode.Ldelem_Ref:
+ {
+ ArrayType arrayType = InferTypeForExpression(expr.Arguments[0], null) as ArrayType;
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments[1], typeSystem.Int32);
+ }
+ return arrayType != null ? arrayType.ElementType : null;
+ }
+ case ILCode.Ldelem_Any:
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments[1], typeSystem.Int32);
+ }
+ return (TypeReference)expr.Operand;
+ case ILCode.Ldelema:
+ {
+ ArrayType arrayType = InferTypeForExpression(expr.Arguments[0], null) as ArrayType;
+ if (forceInferChildren)
+ InferTypeForExpression(expr.Arguments[1], typeSystem.Int32);
+ return arrayType != null ? new ByReferenceType(arrayType.ElementType) : null;
+ }
+ case ILCode.Stelem_I:
+ case ILCode.Stelem_I1:
+ case ILCode.Stelem_I2:
+ case ILCode.Stelem_I4:
+ case ILCode.Stelem_I8:
+ case ILCode.Stelem_R4:
+ case ILCode.Stelem_R8:
+ case ILCode.Stelem_Ref:
+ case ILCode.Stelem_Any:
+ {
+ ArrayType arrayType = InferTypeForExpression(expr.Arguments[0], null) as ArrayType;
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments[1], typeSystem.Int32);
+ if (arrayType != null) {
+ InferTypeForExpression(expr.Arguments[2], arrayType.ElementType);
+ }
+ }
+ return arrayType != null ? arrayType.ElementType : null;
+ }
+ #endregion
+ #region Conversion instructions
+ case ILCode.Conv_I1:
+ case ILCode.Conv_Ovf_I1:
+ case ILCode.Conv_Ovf_I1_Un:
+ return HandleConversion(8, true, expr.Arguments[0], expectedType, typeSystem.SByte);
+ case ILCode.Conv_I2:
+ case ILCode.Conv_Ovf_I2:
+ case ILCode.Conv_Ovf_I2_Un:
+ return HandleConversion(16, true, expr.Arguments[0], expectedType, typeSystem.Int16);
+ case ILCode.Conv_I4:
+ case ILCode.Conv_Ovf_I4:
+ case ILCode.Conv_Ovf_I4_Un:
+ return HandleConversion(32, true, expr.Arguments[0], expectedType, typeSystem.Int32);
+ case ILCode.Conv_I8:
+ case ILCode.Conv_Ovf_I8:
+ case ILCode.Conv_Ovf_I8_Un:
+ return HandleConversion(64, true, expr.Arguments[0], expectedType, typeSystem.Int64);
+ case ILCode.Conv_U1:
+ case ILCode.Conv_Ovf_U1:
+ case ILCode.Conv_Ovf_U1_Un:
+ return HandleConversion(8, false, expr.Arguments[0], expectedType, typeSystem.Byte);
+ case ILCode.Conv_U2:
+ case ILCode.Conv_Ovf_U2:
+ case ILCode.Conv_Ovf_U2_Un:
+ return HandleConversion(16, false, expr.Arguments[0], expectedType, typeSystem.UInt16);
+ case ILCode.Conv_U4:
+ case ILCode.Conv_Ovf_U4:
+ case ILCode.Conv_Ovf_U4_Un:
+ return HandleConversion(32, false, expr.Arguments[0], expectedType, typeSystem.UInt32);
+ case ILCode.Conv_U8:
+ case ILCode.Conv_Ovf_U8:
+ case ILCode.Conv_Ovf_U8_Un:
+ return HandleConversion(64, false, expr.Arguments[0], expectedType, typeSystem.UInt64);
+ case ILCode.Conv_I:
+ case ILCode.Conv_Ovf_I:
+ case ILCode.Conv_Ovf_I_Un:
+ return HandleConversion(NativeInt, true, expr.Arguments[0], expectedType, typeSystem.IntPtr);
+ case ILCode.Conv_U:
+ case ILCode.Conv_Ovf_U:
+ case ILCode.Conv_Ovf_U_Un:
+ return HandleConversion(NativeInt, false, expr.Arguments[0], expectedType, typeSystem.UIntPtr);
+ case ILCode.Conv_R4:
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments[0], typeSystem.Single);
+ }
+ return typeSystem.Single;
+ case ILCode.Conv_R8:
+ if (forceInferChildren) {
+ InferTypeForExpression(expr.Arguments[0], typeSystem.Double);
+ }
+ return typeSystem.Double;
+ case ILCode.Conv_R_Un:
+ return (expectedType != null && expectedType.MetadataType == MetadataType.Single) ? typeSystem.Single : typeSystem.Double;
+ case ILCode.Castclass:
+ case ILCode.Unbox_Any:
+ return (TypeReference)expr.Operand;
+ case ILCode.Unbox:
+ return new ByReferenceType((TypeReference)expr.Operand);
+ case ILCode.Isinst:
+ {
+ // isinst performs the equivalent of a cast only for reference types;
+ // value types still need to be unboxed after an isinst instruction
+ TypeReference tr = (TypeReference)expr.Operand;
+ return tr.IsValueType ? typeSystem.Object : tr;
+ }
+ case ILCode.Box:
+ {
+ var tr = (TypeReference)expr.Operand;
+ if (forceInferChildren)
+ InferTypeForExpression(expr.Arguments.Single(), tr);
+ return tr.IsValueType ? typeSystem.Object : tr;
+ }
+ #endregion
+ #region Comparison instructions
+ case ILCode.Ceq:
+ case ILCode.Cne:
+ if (forceInferChildren)
+ InferArgumentsInBinaryOperator(expr, null, null);
+ return typeSystem.Boolean;
+ case ILCode.Clt:
+ case ILCode.Cgt:
+ case ILCode.Cle:
+ case ILCode.Cge:
+ if (forceInferChildren)
+ InferArgumentsInBinaryOperator(expr, true, null);
+ return typeSystem.Boolean;
+ case ILCode.Clt_Un:
+ case ILCode.Cgt_Un:
+ case ILCode.Cle_Un:
+ case ILCode.Cge_Un:
+ if (forceInferChildren)
+ InferArgumentsInBinaryOperator(expr, false, null);
+ return typeSystem.Boolean;
+ #endregion
+ #region Branch instructions
+ case ILCode.Brtrue:
+ if (forceInferChildren)
+ InferTypeForExpression(expr.Arguments.Single(), typeSystem.Boolean);
+ return null;
+ case ILCode.Br:
+ case ILCode.Leave:
+ case ILCode.Endfinally:
+ case ILCode.Switch:
+ case ILCode.Throw:
+ case ILCode.Rethrow:
+ case ILCode.LoopOrSwitchBreak:
+ case ILCode.LoopContinue:
+ case ILCode.YieldBreak:
+ return null;
+ case ILCode.Ret:
+ if (forceInferChildren && expr.Arguments.Count == 1) {
+ TypeReference returnType = context.CurrentMethod.ReturnType;
+ if (context.CurrentMethodIsAsync && returnType != null && returnType.Namespace == "System.Threading.Tasks") {
+ if (returnType.Name == "Task") {
+ returnType = typeSystem.Void;
+ } else if (returnType.Name == "Task`1" && returnType.IsGenericInstance) {
+ returnType = ((GenericInstanceType)returnType).GenericArguments[0];
+ }
+ }
+ InferTypeForExpression(expr.Arguments[0], returnType);
+ }
+ return null;
+ case ILCode.YieldReturn:
+ if (forceInferChildren) {
+ GenericInstanceType genericType = context.CurrentMethod.ReturnType as GenericInstanceType;
+ if (genericType != null) { // IEnumerable<T> or IEnumerator<T>
+ InferTypeForExpression(expr.Arguments[0], genericType.GenericArguments[0]);
+ } else { // non-generic IEnumerable or IEnumerator
+ InferTypeForExpression(expr.Arguments[0], typeSystem.Object);
+ }
+ }
+ return null;
+ case ILCode.Await:
+ {
+ TypeReference taskType = InferTypeForExpression(expr.Arguments[0], null);
+ if (taskType != null && taskType.Name == "Task`1" && taskType.IsGenericInstance && taskType.Namespace == "System.Threading.Tasks") {
+ return ((GenericInstanceType)taskType).GenericArguments[0];
+ }
+ return null;
+ }
+ #endregion
+ case ILCode.Pop:
+ return null;
+ case ILCode.Wrap:
+ case ILCode.Dup:
+ {
+ var arg = expr.Arguments.Single();
+ return arg.ExpectedType = InferTypeForExpression(arg, expectedType);
+ }
+ default:
+ Debug.WriteLine("Type Inference: Can't handle " + expr.Code.GetName());
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Wraps 'type' in a ByReferenceType if it is a value type. If a constrained prefix is specified,
+ /// returns the constrained type wrapped in a ByReferenceType.
+ /// </summary>
+ TypeReference MakeRefIfValueType(TypeReference type, ILExpressionPrefix constrainedPrefix)
+ {
+ if (constrainedPrefix != null)
+ return new ByReferenceType((TypeReference)constrainedPrefix.Operand);
+ if (type.IsValueType)
+ return new ByReferenceType(type);
+ else
+ return type;
+ }
+
+ /// <summary>
+ /// Promotes primitive types smaller than int32 to int32.
+ /// </summary>
+ /// <remarks>
+ /// Always promotes to signed int32.
+ /// </remarks>
+ TypeReference NumericPromotion(TypeReference type)
+ {
+ if (type == null)
+ return null;
+ switch (type.MetadataType) {
+ case MetadataType.SByte:
+ case MetadataType.Int16:
+ case MetadataType.Byte:
+ case MetadataType.UInt16:
+ return typeSystem.Int32;
+ default:
+ return type;
+ }
+ }
+
+ TypeReference HandleConversion(int targetBitSize, bool targetSigned, ILExpression arg, TypeReference expectedType, TypeReference targetType)
+ {
+ if (targetBitSize >= NativeInt && expectedType is PointerType) {
+ InferTypeForExpression(arg, expectedType);
+ return expectedType;
+ }
+ TypeReference argType = InferTypeForExpression(arg, null);
+ if (targetBitSize >= NativeInt && argType is ByReferenceType) {
+ // conv instructions on managed references mean that the GC should stop tracking them, so they become pointers:
+ PointerType ptrType = new PointerType(((ByReferenceType)argType).ElementType);
+ InferTypeForExpression(arg, ptrType);
+ return ptrType;
+ } else if (targetBitSize >= NativeInt && argType is PointerType) {
+ return argType;
+ }
+ TypeReference resultType = (GetInformationAmount(expectedType) == targetBitSize && IsSigned(expectedType) == targetSigned) ? expectedType : targetType;
+ arg.ExpectedType = resultType; // store the expected type in the argument so that AstMethodBodyBuilder will insert a cast
+ return resultType;
+ }
+
+ public static TypeReference GetFieldType(FieldReference fieldReference)
+ {
+ return SubstituteTypeArgs(UnpackModifiers(fieldReference.FieldType), fieldReference);
+ }
+
+ public static TypeReference SubstituteTypeArgs(TypeReference type, MemberReference member)
+ {
+ if (type is TypeSpecification) {
+ ArrayType arrayType = type as ArrayType;
+ if (arrayType != null) {
+ TypeReference elementType = SubstituteTypeArgs(arrayType.ElementType, member);
+ if (elementType != arrayType.ElementType) {
+ ArrayType newArrayType = new ArrayType(elementType);
+ newArrayType.Dimensions.Clear(); // remove the single dimension that Cecil adds by default
+ foreach (ArrayDimension d in arrayType.Dimensions)
+ newArrayType.Dimensions.Add(d);
+ return newArrayType;
+ } else {
+ return type;
+ }
+ }
+ ByReferenceType refType = type as ByReferenceType;
+ if (refType != null) {
+ TypeReference elementType = SubstituteTypeArgs(refType.ElementType, member);
+ return elementType != refType.ElementType ? new ByReferenceType(elementType) : type;
+ }
+ GenericInstanceType giType = type as GenericInstanceType;
+ if (giType != null) {
+ GenericInstanceType newType = new GenericInstanceType(giType.ElementType);
+ bool isChanged = false;
+ for (int i = 0; i < giType.GenericArguments.Count; i++) {
+ newType.GenericArguments.Add(SubstituteTypeArgs(giType.GenericArguments[i], member));
+ isChanged |= newType.GenericArguments[i] != giType.GenericArguments[i];
+ }
+ return isChanged ? newType : type;
+ }
+ OptionalModifierType optmodType = type as OptionalModifierType;
+ if (optmodType != null) {
+ TypeReference elementType = SubstituteTypeArgs(optmodType.ElementType, member);
+ return elementType != optmodType.ElementType ? new OptionalModifierType(optmodType.ModifierType, elementType) : type;
+ }
+ RequiredModifierType reqmodType = type as RequiredModifierType;
+ if (reqmodType != null) {
+ TypeReference elementType = SubstituteTypeArgs(reqmodType.ElementType, member);
+ return elementType != reqmodType.ElementType ? new RequiredModifierType(reqmodType.ModifierType, elementType) : type;
+ }
+ PointerType ptrType = type as PointerType;
+ if (ptrType != null) {
+ TypeReference elementType = SubstituteTypeArgs(ptrType.ElementType, member);
+ return elementType != ptrType.ElementType ? new PointerType(elementType) : type;
+ }
+ }
+ GenericParameter gp = type as GenericParameter;
+ if (gp != null) {
+ if (member.DeclaringType is ArrayType) {
+ return ((ArrayType)member.DeclaringType).ElementType;
+ } else if (gp.Owner.GenericParameterType == GenericParameterType.Method) {
+ return ((GenericInstanceMethod)member).GenericArguments[gp.Position];
+ } else {
+ return ((GenericInstanceType)member.DeclaringType).GenericArguments[gp.Position];
+ }
+ }
+ return type;
+ }
+
+ static TypeReference UnpackPointer(TypeReference pointerOrManagedReference)
+ {
+ ByReferenceType refType = pointerOrManagedReference as ByReferenceType;
+ if (refType != null)
+ return refType.ElementType;
+ PointerType ptrType = pointerOrManagedReference as PointerType;
+ if (ptrType != null)
+ return ptrType.ElementType;
+ return null;
+ }
+
+ internal static TypeReference UnpackModifiers(TypeReference type)
+ {
+ while (type is OptionalModifierType || type is RequiredModifierType)
+ type = ((TypeSpecification)type).ElementType;
+ return type;
+ }
+
+ static TypeReference GetNullableTypeArgument(TypeReference type)
+ {
+ var t = type as GenericInstanceType;
+ return IsNullableType(t) ? t.GenericArguments[0] : type;
+ }
+
+ GenericInstanceType CreateNullableType(TypeReference type)
+ {
+ if (type == null) return null;
+ var t = new GenericInstanceType(new TypeReference("System", "Nullable`1", module, module.TypeSystem.Corlib, true));
+ t.GenericArguments.Add(type);
+ return t;
+ }
+
+ TypeReference InferArgumentsInBinaryOperator(ILExpression expr, bool? isSigned, TypeReference expectedType)
+ {
+ return InferBinaryArguments(expr.Arguments[0], expr.Arguments[1], expectedType);
+ }
+
+ TypeReference InferArgumentsInAddition(ILExpression expr, bool? isSigned, TypeReference expectedType)
+ {
+ ILExpression left = expr.Arguments[0];
+ ILExpression right = expr.Arguments[1];
+ TypeReference leftPreferred = DoInferTypeForExpression(left, expectedType);
+ if (leftPreferred is PointerType) {
+ left.InferredType = left.ExpectedType = leftPreferred;
+ InferTypeForExpression(right, null);
+ return leftPreferred;
+ }
+ if (IsEnum(leftPreferred)) {
+ //E+U=E
+ left.InferredType = left.ExpectedType = leftPreferred;
+ InferTypeForExpression(right, GetEnumUnderlyingType(leftPreferred));
+ return leftPreferred;
+ }
+ TypeReference rightPreferred = DoInferTypeForExpression(right, expectedType);
+ if (rightPreferred is PointerType) {
+ InferTypeForExpression(left, null);
+ right.InferredType = right.ExpectedType = rightPreferred;
+ return rightPreferred;
+ }
+ if (IsEnum(rightPreferred)) {
+ //U+E=E
+ right.InferredType = right.ExpectedType = rightPreferred;
+ InferTypeForExpression(left, GetEnumUnderlyingType(rightPreferred));
+ return rightPreferred;
+ }
+ return InferBinaryArguments(left, right, expectedType, leftPreferred: leftPreferred, rightPreferred: rightPreferred);
+ }
+
+ TypeReference InferArgumentsInSubtraction(ILExpression expr, bool? isSigned, TypeReference expectedType)
+ {
+ ILExpression left = expr.Arguments[0];
+ ILExpression right = expr.Arguments[1];
+ TypeReference leftPreferred = DoInferTypeForExpression(left, expectedType);
+ if (leftPreferred is PointerType) {
+ left.InferredType = left.ExpectedType = leftPreferred;
+ InferTypeForExpression(right, null);
+ return leftPreferred;
+ }
+ if (IsEnum(leftPreferred)) {
+ if (expectedType != null && IsEnum(expectedType)) {
+ // E-U=E
+ left.InferredType = left.ExpectedType = leftPreferred;
+ InferTypeForExpression(right, GetEnumUnderlyingType(leftPreferred));
+ return leftPreferred;
+ } else {
+ // E-E=U
+ left.InferredType = left.ExpectedType = leftPreferred;
+ InferTypeForExpression(right, leftPreferred);
+ return GetEnumUnderlyingType(leftPreferred);
+ }
+ }
+ return InferBinaryArguments(left, right, expectedType, leftPreferred: leftPreferred);
+ }
+
+ TypeReference InferBinaryArguments(ILExpression left, ILExpression right, TypeReference expectedType, bool forceInferChildren = false, TypeReference leftPreferred = null, TypeReference rightPreferred = null)
+ {
+ if (leftPreferred == null) leftPreferred = DoInferTypeForExpression(left, expectedType, forceInferChildren);
+ if (rightPreferred == null) rightPreferred = DoInferTypeForExpression(right, expectedType, forceInferChildren);
+ if (IsSameType(leftPreferred, rightPreferred)) {
+ return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = leftPreferred;
+ } else if (IsSameType(rightPreferred, DoInferTypeForExpression(left, rightPreferred, forceInferChildren))) {
+ return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = rightPreferred;
+ } else if (IsSameType(leftPreferred, DoInferTypeForExpression(right, leftPreferred, forceInferChildren))) {
+ // re-infer the left expression with the preferred type to reset any conflicts caused by the rightPreferred type
+ DoInferTypeForExpression(left, leftPreferred, forceInferChildren);
+ return left.InferredType = right.InferredType = left.ExpectedType = right.ExpectedType = leftPreferred;
+ } else {
+ left.ExpectedType = right.ExpectedType = TypeWithMoreInformation(leftPreferred, rightPreferred);
+ left.InferredType = DoInferTypeForExpression(left, left.ExpectedType, forceInferChildren);
+ right.InferredType = DoInferTypeForExpression(right, right.ExpectedType, forceInferChildren);
+ return left.ExpectedType;
+ }
+ }
+
+ TypeReference TypeWithMoreInformation(TypeReference leftPreferred, TypeReference rightPreferred)
+ {
+ int left = GetInformationAmount(leftPreferred);
+ int right = GetInformationAmount(rightPreferred);
+ if (left < right) {
+ return rightPreferred;
+ } else if (left > right) {
+ return leftPreferred;
+ } else {
+ // TODO
+ return leftPreferred;
+ }
+ }
+
+ /// <summary>
+ /// Information amount used for IntPtr.
+ /// </summary>
+ public const int NativeInt = 33; // treat native int as between int32 and int64
+
+ /// <summary>
+ /// Gets the underlying type, if the specified type is an enum.
+ /// Otherwise, returns null.
+ /// </summary>
+ public static TypeReference GetEnumUnderlyingType(TypeReference enumType)
+ {
+ // unfortunately we cannot rely on enumType.IsValueType here - it's not set when the instruction operand is a typeref (as opposed to a typespec)
+ if (enumType != null && !IsArrayPointerOrReference(enumType)) {
+ // value type might be an enum
+ TypeDefinition typeDef = enumType.Resolve() as TypeDefinition;
+ if (typeDef != null && typeDef.IsEnum) {
+ return typeDef.Fields.Single(f => !f.IsStatic).FieldType;
+ }
+ }
+ return null;
+ }
+
+ public static int GetInformationAmount(TypeReference type)
+ {
+ type = GetEnumUnderlyingType(type) ?? type;
+ if (type == null)
+ return 0;
+ switch (type.MetadataType) {
+ case MetadataType.Void:
+ return 0;
+ case MetadataType.Boolean:
+ return 1;
+ case MetadataType.SByte:
+ case MetadataType.Byte:
+ return 8;
+ case MetadataType.Char:
+ case MetadataType.Int16:
+ case MetadataType.UInt16:
+ return 16;
+ case MetadataType.Int32:
+ case MetadataType.UInt32:
+ case MetadataType.Single:
+ return 32;
+ case MetadataType.Int64:
+ case MetadataType.UInt64:
+ case MetadataType.Double:
+ return 64;
+ case MetadataType.IntPtr:
+ case MetadataType.UIntPtr:
+ return NativeInt;
+ default:
+ return 100; // we consider structs/objects to have more information than any primitives
+ }
+ }
+
+ public static bool IsBoolean(TypeReference type)
+ {
+ return type != null && type.MetadataType == MetadataType.Boolean;
+ }
+
+ public static bool IsIntegerOrEnum(TypeReference type)
+ {
+ return IsSigned(type) != null;
+ }
+
+ public static bool IsEnum(TypeReference type)
+ {
+ // Arrays/Pointers/ByReference resolve to their element type, but we don't want to consider those to be enums
+ // However, GenericInstanceTypes, ModOpts etc. should be considered enums.
+ if (type == null || IsArrayPointerOrReference(type))
+ return false;
+ // unfortunately we cannot rely on type.IsValueType here - it's not set when the instruction operand is a typeref (as opposed to a typespec)
+ TypeDefinition typeDef = type.Resolve() as TypeDefinition;
+ return typeDef != null && typeDef.IsEnum;
+ }
+
+ static bool? IsSigned(TypeReference type)
+ {
+ type = GetEnumUnderlyingType(type) ?? type;
+ if (type == null)
+ return null;
+ switch (type.MetadataType) {
+ case MetadataType.SByte:
+ case MetadataType.Int16:
+ case MetadataType.Int32:
+ case MetadataType.Int64:
+ case MetadataType.IntPtr:
+ return true;
+ case MetadataType.Byte:
+ case MetadataType.Char:
+ case MetadataType.UInt16:
+ case MetadataType.UInt32:
+ case MetadataType.UInt64:
+ case MetadataType.UIntPtr:
+ return false;
+ default:
+ return null;
+ }
+ }
+
+ static bool OperandFitsInType(TypeReference type, int num)
+ {
+ type = GetEnumUnderlyingType(type) ?? type;
+ switch (type.MetadataType) {
+ case MetadataType.SByte:
+ return sbyte.MinValue <= num && num <= sbyte.MaxValue;
+ case MetadataType.Int16:
+ return short.MinValue <= num && num <= short.MaxValue;
+ case MetadataType.Byte:
+ return byte.MinValue <= num && num <= byte.MaxValue;
+ case MetadataType.Char:
+ return char.MinValue <= num && num <= char.MaxValue;
+ case MetadataType.UInt16:
+ return ushort.MinValue <= num && num <= ushort.MaxValue;
+ default:
+ return true;
+ }
+ }
+
+ static bool IsArrayPointerOrReference(TypeReference type)
+ {
+ TypeSpecification typeSpec = type as TypeSpecification;
+ while (typeSpec != null) {
+ if (typeSpec is ArrayType || typeSpec is PointerType || typeSpec is ByReferenceType)
+ return true;
+ typeSpec = typeSpec.ElementType as TypeSpecification;
+ }
+ return false;
+ }
+
+ internal static bool IsNullableType(TypeReference type)
+ {
+ return type != null && type.Name == "Nullable`1" && type.Namespace == "System";
+ }
+
+ public static TypeCode GetTypeCode(TypeReference type)
+ {
+ if (type == null)
+ return TypeCode.Empty;
+ switch (type.MetadataType) {
+ case MetadataType.Boolean:
+ return TypeCode.Boolean;
+ case MetadataType.Char:
+ return TypeCode.Char;
+ case MetadataType.SByte:
+ return TypeCode.SByte;
+ case MetadataType.Byte:
+ return TypeCode.Byte;
+ case MetadataType.Int16:
+ return TypeCode.Int16;
+ case MetadataType.UInt16:
+ return TypeCode.UInt16;
+ case MetadataType.Int32:
+ return TypeCode.Int32;
+ case MetadataType.UInt32:
+ return TypeCode.UInt32;
+ case MetadataType.Int64:
+ return TypeCode.Int64;
+ case MetadataType.UInt64:
+ return TypeCode.UInt64;
+ case MetadataType.Single:
+ return TypeCode.Single;
+ case MetadataType.Double:
+ return TypeCode.Double;
+ case MetadataType.String:
+ return TypeCode.String;
+ default:
+ return TypeCode.Object;
+ }
+ }
+
+ /// <summary>
+ /// Clears the type inference data on the method.
+ /// </summary>
+ public static void Reset(ILBlock method)
+ {
+ foreach (ILExpression expr in method.GetSelfAndChildrenRecursive<ILExpression>()) {
+ expr.InferredType = null;
+ expr.ExpectedType = null;
+ ILVariable v = expr.Operand as ILVariable;
+ if (v != null && v.IsGenerated)
+ v.Type = null;
+ }
+ }
+
+ public static bool IsSameType(TypeReference type1, TypeReference type2)
+ {
+ if (type1 == type2)
+ return true;
+ if (type1 == null || type2 == null)
+ return false;
+ return type1.FullName == type2.FullName; // TODO: implement this more efficiently?
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs
new file mode 100644
index 00000000..7a9a09ed
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs
@@ -0,0 +1,635 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Mono.Cecil;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ internal class YieldReturnDecompiler
+ {
+ // For a description on the code generated by the C# compiler for yield return:
+ // http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx
+
+ // The idea here is:
+ // - Figure out whether the current method is instanciating an enumerator
+ // - Figure out which of the fields is the state field
+ // - Construct an exception table based on states. This allows us to determine, for each state, what the parent try block is.
+
+ // See http://community.sharpdevelop.net/blogs/danielgrunwald/archive/2011/03/06/ilspy-yield-return.aspx
+ // for a description of this step.
+
+ DecompilerContext context;
+ TypeDefinition enumeratorType;
+ MethodDefinition enumeratorCtor;
+ MethodDefinition disposeMethod;
+ FieldDefinition stateField;
+ FieldDefinition currentField;
+ Dictionary<FieldDefinition, ILVariable> fieldToParameterMap = new Dictionary<FieldDefinition, ILVariable>();
+ List<ILNode> newBody;
+
+ #region Run() method
+ public static void Run(DecompilerContext context, ILBlock method)
+ {
+ if (!context.Settings.YieldReturn)
+ return; // abort if enumerator decompilation is disabled
+ var yrd = new YieldReturnDecompiler();
+ yrd.context = context;
+ if (!yrd.MatchEnumeratorCreationPattern(method))
+ return;
+ yrd.enumeratorType = yrd.enumeratorCtor.DeclaringType;
+ #if DEBUG
+ if (Debugger.IsAttached) {
+ yrd.Run();
+ } else {
+ #endif
+ try {
+ yrd.Run();
+ } catch (SymbolicAnalysisFailedException) {
+ return;
+ }
+ #if DEBUG
+ }
+ #endif
+ method.Body.Clear();
+ method.EntryGoto = null;
+ method.Body.AddRange(yrd.newBody);
+
+ // Repeat the inlining/copy propagation optimization because the conversion of field access
+ // to local variables can open up additional inlining possibilities.
+ ILInlining inlining = new ILInlining(method);
+ inlining.InlineAllVariables();
+ inlining.CopyPropagation();
+ }
+
+ void Run()
+ {
+ AnalyzeCtor();
+ AnalyzeCurrentProperty();
+ ResolveIEnumerableIEnumeratorFieldMapping();
+ ConstructExceptionTable();
+ AnalyzeMoveNext();
+ TranslateFieldsToLocalAccess();
+ }
+ #endregion
+
+ #region Match the enumerator creation pattern
+ bool MatchEnumeratorCreationPattern(ILBlock method)
+ {
+ if (method.Body.Count == 0)
+ return false;
+ ILExpression newObj;
+ if (method.Body.Count == 1) {
+ // ret(newobj(...))
+ if (method.Body[0].Match(ILCode.Ret, out newObj))
+ return MatchEnumeratorCreationNewObj(newObj, out enumeratorCtor);
+ else
+ return false;
+ }
+ // stloc(var_1, newobj(..)
+ ILVariable var1;
+ if (!method.Body[0].Match(ILCode.Stloc, out var1, out newObj))
+ return false;
+ if (!MatchEnumeratorCreationNewObj(newObj, out enumeratorCtor))
+ return false;
+
+ int i;
+ for (i = 1; i < method.Body.Count; i++) {
+ // stfld(..., ldloc(var_1), ldloc(parameter))
+ FieldReference storedField;
+ ILExpression ldloc, loadParameter;
+ if (!method.Body[i].Match(ILCode.Stfld, out storedField, out ldloc, out loadParameter))
+ break;
+ ILVariable loadedVar, loadedArg;
+ if (!ldloc.Match(ILCode.Ldloc, out loadedVar) || !loadParameter.Match(ILCode.Ldloc, out loadedArg))
+ return false;
+ storedField = GetFieldDefinition(storedField);
+ if (loadedVar != var1 || storedField == null || !loadedArg.IsParameter)
+ return false;
+ fieldToParameterMap[(FieldDefinition)storedField] = loadedArg;
+ }
+ ILVariable var2;
+ ILExpression ldlocForStloc2;
+ if (i < method.Body.Count && method.Body[i].Match(ILCode.Stloc, out var2, out ldlocForStloc2)) {
+ // stloc(var_2, ldloc(var_1))
+ if (ldlocForStloc2.Code != ILCode.Ldloc || ldlocForStloc2.Operand != var1)
+ return false;
+ i++;
+ } else {
+ // the compiler might skip the above instruction in release builds; in that case, it directly returns stloc.Operand
+ var2 = var1;
+ }
+ ILExpression retArg;
+ if (i < method.Body.Count && method.Body[i].Match(ILCode.Ret, out retArg)) {
+ // ret(ldloc(var_2))
+ if (retArg.Code == ILCode.Ldloc && retArg.Operand == var2) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static FieldDefinition GetFieldDefinition(FieldReference field)
+ {
+ return CecilExtensions.ResolveWithinSameModule(field);
+ }
+
+ static MethodDefinition GetMethodDefinition(MethodReference method)
+ {
+ return CecilExtensions.ResolveWithinSameModule(method);
+ }
+
+ bool MatchEnumeratorCreationNewObj(ILExpression expr, out MethodDefinition ctor)
+ {
+ // newobj(CurrentType/...::.ctor, ldc.i4(-2))
+ ctor = null;
+ if (expr.Code != ILCode.Newobj || expr.Arguments.Count != 1)
+ return false;
+ if (expr.Arguments[0].Code != ILCode.Ldc_I4)
+ return false;
+ int initialState = (int)expr.Arguments[0].Operand;
+ if (!(initialState == -2 || initialState == 0))
+ return false;
+ ctor = GetMethodDefinition(expr.Operand as MethodReference);
+ if (ctor == null || ctor.DeclaringType.DeclaringType != context.CurrentType)
+ return false;
+ return IsCompilerGeneratorEnumerator(ctor.DeclaringType);
+ }
+
+ public static bool IsCompilerGeneratorEnumerator(TypeDefinition type)
+ {
+ if (!(type.DeclaringType != null && type.IsCompilerGenerated()))
+ return false;
+ foreach (TypeReference i in type.Interfaces) {
+ if (i.Namespace == "System.Collections" && i.Name == "IEnumerator")
+ return true;
+ }
+ return false;
+ }
+ #endregion
+
+ #region Figure out what the 'state' field is (analysis of .ctor())
+ /// <summary>
+ /// Looks at the enumerator's ctor and figures out which of the fields holds the state.
+ /// </summary>
+ void AnalyzeCtor()
+ {
+ ILBlock method = CreateILAst(enumeratorCtor);
+
+ foreach (ILNode node in method.Body) {
+ FieldReference field;
+ ILExpression instExpr;
+ ILExpression stExpr;
+ ILVariable arg;
+ if (node.Match(ILCode.Stfld, out field, out instExpr, out stExpr) &&
+ instExpr.MatchThis() &&
+ stExpr.Match(ILCode.Ldloc, out arg) &&
+ arg.IsParameter && arg.OriginalParameter.Index == 0)
+ {
+ stateField = GetFieldDefinition(field);
+ }
+ }
+ if (stateField == null)
+ throw new SymbolicAnalysisFailedException();
+ }
+
+ /// <summary>
+ /// Creates ILAst for the specified method, optimized up to before the 'YieldReturn' step.
+ /// </summary>
+ ILBlock CreateILAst(MethodDefinition method)
+ {
+ if (method == null || !method.HasBody)
+ throw new SymbolicAnalysisFailedException();
+
+ ILBlock ilMethod = new ILBlock();
+ ILAstBuilder astBuilder = new ILAstBuilder();
+ ilMethod.Body = astBuilder.Build(method, true, context);
+ ILAstOptimizer optimizer = new ILAstOptimizer();
+ optimizer.Optimize(context, ilMethod, ILAstOptimizationStep.YieldReturn);
+ return ilMethod;
+ }
+ #endregion
+
+ #region Figure out what the 'current' field is (analysis of get_Current())
+ /// <summary>
+ /// Looks at the enumerator's get_Current method and figures out which of the fields holds the current value.
+ /// </summary>
+ void AnalyzeCurrentProperty()
+ {
+ MethodDefinition getCurrentMethod = enumeratorType.Methods.FirstOrDefault(
+ m => m.Name.StartsWith("System.Collections.Generic.IEnumerator", StringComparison.Ordinal)
+ && m.Name.EndsWith(".get_Current", StringComparison.Ordinal));
+ ILBlock method = CreateILAst(getCurrentMethod);
+ if (method.Body.Count == 1) {
+ // release builds directly return the current field
+ ILExpression retExpr;
+ FieldReference field;
+ ILExpression ldFromObj;
+ if (method.Body[0].Match(ILCode.Ret, out retExpr) &&
+ retExpr.Match(ILCode.Ldfld, out field, out ldFromObj) &&
+ ldFromObj.MatchThis())
+ {
+ currentField = GetFieldDefinition(field);
+ }
+ } else if (method.Body.Count == 2) {
+ ILVariable v, v2;
+ ILExpression stExpr;
+ FieldReference field;
+ ILExpression ldFromObj;
+ ILExpression retExpr;
+ if (method.Body[0].Match(ILCode.Stloc, out v, out stExpr) &&
+ stExpr.Match(ILCode.Ldfld, out field, out ldFromObj) &&
+ ldFromObj.MatchThis() &&
+ method.Body[1].Match(ILCode.Ret, out retExpr) &&
+ retExpr.Match(ILCode.Ldloc, out v2) &&
+ v == v2)
+ {
+ currentField = GetFieldDefinition(field);
+ }
+ }
+ if (currentField == null)
+ throw new SymbolicAnalysisFailedException();
+ }
+ #endregion
+
+ #region Figure out the mapping of IEnumerable fields to IEnumerator fields (analysis of GetEnumerator())
+ void ResolveIEnumerableIEnumeratorFieldMapping()
+ {
+ MethodDefinition getEnumeratorMethod = enumeratorType.Methods.FirstOrDefault(
+ m => m.Name.StartsWith("System.Collections.Generic.IEnumerable", StringComparison.Ordinal)
+ && m.Name.EndsWith(".GetEnumerator", StringComparison.Ordinal));
+ if (getEnumeratorMethod == null)
+ return; // no mappings (maybe it's just an IEnumerator implementation?)
+
+ ILBlock method = CreateILAst(getEnumeratorMethod);
+ foreach (ILNode node in method.Body) {
+ FieldReference stField;
+ ILExpression stToObj;
+ ILExpression stExpr;
+ FieldReference ldField;
+ ILExpression ldFromObj;
+ if (node.Match(ILCode.Stfld, out stField, out stToObj, out stExpr) &&
+ stExpr.Match(ILCode.Ldfld, out ldField, out ldFromObj) &&
+ ldFromObj.MatchThis())
+ {
+ FieldDefinition storedField = GetFieldDefinition(stField);
+ FieldDefinition loadedField = GetFieldDefinition(ldField);
+ if (storedField != null && loadedField != null) {
+ ILVariable mappedParameter;
+ if (fieldToParameterMap.TryGetValue(loadedField, out mappedParameter))
+ fieldToParameterMap[storedField] = mappedParameter;
+ }
+ }
+ }
+ }
+ #endregion
+
+ #region Construction of the exception table (analysis of Dispose())
+ // We construct the exception table by analyzing the enumerator's Dispose() method.
+
+ // Assumption: there are no loops/backward jumps
+ // We 'run' the code, with "state" being a symbolic variable
+ // so it can form expressions like "state + x" (when there's a sub instruction)
+ // For each instruction, we maintain a list of value ranges for state for which the instruction is reachable.
+ // This is (int.MinValue, int.MaxValue) for the first instruction.
+ // These ranges are propagated depending on the conditional jumps performed by the code.
+
+ Dictionary<MethodDefinition, StateRange> finallyMethodToStateRange;
+
+ void ConstructExceptionTable()
+ {
+ disposeMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "System.IDisposable.Dispose");
+ ILBlock ilMethod = CreateILAst(disposeMethod);
+
+ var rangeAnalysis = new StateRangeAnalysis(ilMethod.Body[0], StateRangeAnalysisMode.IteratorDispose, stateField);
+ rangeAnalysis.AssignStateRanges(ilMethod.Body, ilMethod.Body.Count);
+ finallyMethodToStateRange = rangeAnalysis.finallyMethodToStateRange;
+
+ // Now look at the finally blocks:
+ foreach (var tryFinally in ilMethod.GetSelfAndChildrenRecursive<ILTryCatchBlock>()) {
+ var range = rangeAnalysis.ranges[tryFinally.TryBlock.Body[0]];
+ var finallyBody = tryFinally.FinallyBlock.Body;
+ if (finallyBody.Count != 2)
+ throw new SymbolicAnalysisFailedException();
+ ILExpression call = finallyBody[0] as ILExpression;
+ if (call == null || call.Code != ILCode.Call || call.Arguments.Count != 1)
+ throw new SymbolicAnalysisFailedException();
+ if (!call.Arguments[0].MatchThis())
+ throw new SymbolicAnalysisFailedException();
+ if (!finallyBody[1].Match(ILCode.Endfinally))
+ throw new SymbolicAnalysisFailedException();
+
+ MethodDefinition mdef = GetMethodDefinition(call.Operand as MethodReference);
+ if (mdef == null || finallyMethodToStateRange.ContainsKey(mdef))
+ throw new SymbolicAnalysisFailedException();
+ finallyMethodToStateRange.Add(mdef, range);
+ }
+ rangeAnalysis = null;
+ }
+ #endregion
+
+ #region Analysis of MoveNext()
+ ILVariable returnVariable;
+ ILLabel returnLabel;
+ ILLabel returnFalseLabel;
+
+ void AnalyzeMoveNext()
+ {
+ MethodDefinition moveNextMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "MoveNext");
+ ILBlock ilMethod = CreateILAst(moveNextMethod);
+
+ if (ilMethod.Body.Count == 0)
+ throw new SymbolicAnalysisFailedException();
+ ILExpression lastReturnArg;
+ if (!ilMethod.Body.Last().Match(ILCode.Ret, out lastReturnArg))
+ throw new SymbolicAnalysisFailedException();
+
+ // There are two possibilities:
+ if (lastReturnArg.Code == ILCode.Ldloc) {
+ // a) the compiler uses a variable for returns (in debug builds, or when there are try-finally blocks)
+ returnVariable = (ILVariable)lastReturnArg.Operand;
+ returnLabel = ilMethod.Body.ElementAtOrDefault(ilMethod.Body.Count - 2) as ILLabel;
+ if (returnLabel == null)
+ throw new SymbolicAnalysisFailedException();
+ } else {
+ // b) the compiler directly returns constants
+ returnVariable = null;
+ returnLabel = null;
+ // In this case, the last return must return false.
+ if (lastReturnArg.Code != ILCode.Ldc_I4 || (int)lastReturnArg.Operand != 0)
+ throw new SymbolicAnalysisFailedException();
+ }
+
+ ILTryCatchBlock tryFaultBlock = ilMethod.Body[0] as ILTryCatchBlock;
+ List<ILNode> body;
+ int bodyLength;
+ if (tryFaultBlock != null) {
+ // there are try-finally blocks
+ if (returnVariable == null) // in this case, we must use a return variable
+ throw new SymbolicAnalysisFailedException();
+ // must be a try-fault block:
+ if (tryFaultBlock.CatchBlocks.Count != 0 || tryFaultBlock.FinallyBlock != null || tryFaultBlock.FaultBlock == null)
+ throw new SymbolicAnalysisFailedException();
+
+ ILBlock faultBlock = tryFaultBlock.FaultBlock;
+ // Ensure the fault block contains the call to Dispose().
+ if (faultBlock.Body.Count != 2)
+ throw new SymbolicAnalysisFailedException();
+ MethodReference disposeMethodRef;
+ ILExpression disposeArg;
+ if (!faultBlock.Body[0].Match(ILCode.Call, out disposeMethodRef, out disposeArg))
+ throw new SymbolicAnalysisFailedException();
+ if (GetMethodDefinition(disposeMethodRef) != disposeMethod || !disposeArg.MatchThis())
+ throw new SymbolicAnalysisFailedException();
+ if (!faultBlock.Body[1].Match(ILCode.Endfinally))
+ throw new SymbolicAnalysisFailedException();
+
+ body = tryFaultBlock.TryBlock.Body;
+ bodyLength = body.Count;
+ } else {
+ // no try-finally blocks
+ body = ilMethod.Body;
+ if (returnVariable == null)
+ bodyLength = body.Count - 1; // all except for the return statement
+ else
+ bodyLength = body.Count - 2; // all except for the return label and statement
+ }
+
+ // Now verify that the last instruction in the body is 'ret(false)'
+ if (returnVariable != null) {
+ // If we don't have a return variable, we already verified that above.
+ // If we do have one, check for 'stloc(returnVariable, ldc.i4(0))'
+
+ // Maybe might be a jump to the return label after the stloc:
+ ILExpression leave = body.ElementAtOrDefault(bodyLength - 1) as ILExpression;
+ if (leave != null && (leave.Code == ILCode.Br || leave.Code == ILCode.Leave) && leave.Operand == returnLabel)
+ bodyLength--;
+ ILExpression store0 = body.ElementAtOrDefault(bodyLength - 1) as ILExpression;
+ if (store0 == null || store0.Code != ILCode.Stloc || store0.Operand != returnVariable)
+ throw new SymbolicAnalysisFailedException();
+ if (store0.Arguments[0].Code != ILCode.Ldc_I4 || (int)store0.Arguments[0].Operand != 0)
+ throw new SymbolicAnalysisFailedException();
+
+ bodyLength--; // don't conside the stloc instruction to be part of the body
+ }
+ // The last element in the body usually is a label pointing to the 'ret(false)'
+ returnFalseLabel = body.ElementAtOrDefault(bodyLength - 1) as ILLabel;
+ // Note: in Roslyn-compiled code, returnFalseLabel may be null.
+
+ var rangeAnalysis = new StateRangeAnalysis(body[0], StateRangeAnalysisMode.IteratorMoveNext, stateField);
+ int pos = rangeAnalysis.AssignStateRanges(body, bodyLength);
+ rangeAnalysis.EnsureLabelAtPos(body, ref pos, ref bodyLength);
+
+ var labels = rangeAnalysis.CreateLabelRangeMapping(body, pos, bodyLength);
+ ConvertBody(body, pos, bodyLength, labels);
+ }
+ #endregion
+
+ #region ConvertBody
+ struct SetState
+ {
+ public readonly int NewBodyPos;
+ public readonly int NewState;
+
+ public SetState(int newBodyPos, int newState)
+ {
+ NewBodyPos = newBodyPos;
+ NewState = newState;
+ }
+ }
+
+ void ConvertBody(List<ILNode> body, int startPos, int bodyLength, List<KeyValuePair<ILLabel, StateRange>> labels)
+ {
+ newBody = new List<ILNode>();
+ newBody.Add(MakeGoTo(labels, 0));
+ List<SetState> stateChanges = new List<SetState>();
+ int currentState = -1;
+ // Copy all instructions from the old body to newBody.
+ for (int pos = startPos; pos < bodyLength; pos++) {
+ ILExpression expr = body[pos] as ILExpression;
+ if (expr != null && expr.Code == ILCode.Stfld && expr.Arguments[0].MatchThis()) {
+ // Handle stores to 'state' or 'current'
+ if (GetFieldDefinition(expr.Operand as FieldReference) == stateField) {
+ if (expr.Arguments[1].Code != ILCode.Ldc_I4)
+ throw new SymbolicAnalysisFailedException();
+ currentState = (int)expr.Arguments[1].Operand;
+ stateChanges.Add(new SetState(newBody.Count, currentState));
+ } else if (GetFieldDefinition(expr.Operand as FieldReference) == currentField) {
+ newBody.Add(new ILExpression(ILCode.YieldReturn, null, expr.Arguments[1]));
+ } else {
+ newBody.Add(body[pos]);
+ }
+ } else if (returnVariable != null && expr != null && expr.Code == ILCode.Stloc && expr.Operand == returnVariable) {
+ // handle store+branch to the returnVariable
+ ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression;
+ if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnLabel || expr.Arguments[0].Code != ILCode.Ldc_I4)
+ throw new SymbolicAnalysisFailedException();
+ int val = (int)expr.Arguments[0].Operand;
+ if (val == 0) {
+ newBody.Add(new ILExpression(ILCode.YieldBreak, null));
+ } else if (val == 1) {
+ newBody.Add(MakeGoTo(labels, currentState));
+ } else {
+ throw new SymbolicAnalysisFailedException();
+ }
+ } else if (expr != null && expr.Code == ILCode.Ret) {
+ if (expr.Arguments.Count != 1 || expr.Arguments[0].Code != ILCode.Ldc_I4)
+ throw new SymbolicAnalysisFailedException();
+ // handle direct return (e.g. in release builds)
+ int val = (int)expr.Arguments[0].Operand;
+ if (val == 0) {
+ newBody.Add(new ILExpression(ILCode.YieldBreak, null));
+ } else if (val == 1) {
+ newBody.Add(MakeGoTo(labels, currentState));
+ } else {
+ throw new SymbolicAnalysisFailedException();
+ }
+ } else if (expr != null && expr.Code == ILCode.Call && expr.Arguments.Count == 1 && expr.Arguments[0].MatchThis()) {
+ MethodDefinition method = GetMethodDefinition(expr.Operand as MethodReference);
+ if (method == null)
+ throw new SymbolicAnalysisFailedException();
+ StateRange stateRange;
+ if (method == disposeMethod) {
+ // Explicit call to dispose is used for "yield break;" within the method.
+ ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression;
+ if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnFalseLabel)
+ throw new SymbolicAnalysisFailedException();
+ newBody.Add(new ILExpression(ILCode.YieldBreak, null));
+ } else if (finallyMethodToStateRange.TryGetValue(method, out stateRange)) {
+ // Call to Finally-method
+ int index = stateChanges.FindIndex(ss => stateRange.Contains(ss.NewState));
+ if (index < 0)
+ throw new SymbolicAnalysisFailedException();
+
+ ILLabel label = new ILLabel();
+ label.Name = "JumpOutOfTryFinally" + stateChanges[index].NewState;
+ newBody.Add(new ILExpression(ILCode.Leave, label));
+
+ SetState stateChange = stateChanges[index];
+ // Move all instructions from stateChange.Pos to newBody.Count into a try-block
+ stateChanges.RemoveRange(index, stateChanges.Count - index); // remove all state changes up to the one we found
+ ILTryCatchBlock tryFinally = new ILTryCatchBlock();
+ tryFinally.TryBlock = new ILBlock(newBody.GetRange(stateChange.NewBodyPos, newBody.Count - stateChange.NewBodyPos));
+ newBody.RemoveRange(stateChange.NewBodyPos, newBody.Count - stateChange.NewBodyPos); // remove all nodes that we just moved into the try block
+ tryFinally.CatchBlocks = new List<ILTryCatchBlock.CatchBlock>();
+ tryFinally.FinallyBlock = ConvertFinallyBlock(method);
+ newBody.Add(tryFinally);
+ newBody.Add(label);
+ }
+ } else {
+ newBody.Add(body[pos]);
+ }
+ }
+ newBody.Add(new ILExpression(ILCode.YieldBreak, null));
+ }
+
+ ILExpression MakeGoTo(ILLabel targetLabel)
+ {
+ Debug.Assert(targetLabel != null);
+ if (targetLabel == returnFalseLabel)
+ return new ILExpression(ILCode.YieldBreak, null);
+ else
+ return new ILExpression(ILCode.Br, targetLabel);
+ }
+
+ ILExpression MakeGoTo(List<KeyValuePair<ILLabel, StateRange>> labels, int state)
+ {
+ foreach (var pair in labels) {
+ if (pair.Value.Contains(state))
+ return MakeGoTo(pair.Key);
+ }
+ throw new SymbolicAnalysisFailedException();
+ }
+
+ ILBlock ConvertFinallyBlock(MethodDefinition finallyMethod)
+ {
+ ILBlock block = CreateILAst(finallyMethod);
+ // Get rid of assignment to state
+ FieldReference stfld;
+ List<ILExpression> args;
+ if (block.Body.Count > 0 && block.Body[0].Match(ILCode.Stfld, out stfld, out args)) {
+ if (GetFieldDefinition(stfld) == stateField && args[0].MatchThis())
+ block.Body.RemoveAt(0);
+ }
+ // Convert ret to endfinally
+ foreach (ILExpression expr in block.GetSelfAndChildrenRecursive<ILExpression>()) {
+ if (expr.Code == ILCode.Ret)
+ expr.Code = ILCode.Endfinally;
+ }
+ return block;
+ }
+ #endregion
+
+ #region TranslateFieldsToLocalAccess
+ void TranslateFieldsToLocalAccess()
+ {
+ TranslateFieldsToLocalAccess(newBody, fieldToParameterMap);
+ }
+
+ internal static void TranslateFieldsToLocalAccess(List<ILNode> newBody, Dictionary<FieldDefinition, ILVariable> fieldToParameterMap)
+ {
+ var fieldToLocalMap = new DefaultDictionary<FieldDefinition, ILVariable>(f => new ILVariable { Name = f.Name, Type = f.FieldType });
+ foreach (ILNode node in newBody) {
+ foreach (ILExpression expr in node.GetSelfAndChildrenRecursive<ILExpression>()) {
+ FieldDefinition field = GetFieldDefinition(expr.Operand as FieldReference);
+ if (field != null) {
+ switch (expr.Code) {
+ case ILCode.Ldfld:
+ if (expr.Arguments[0].MatchThis()) {
+ expr.Code = ILCode.Ldloc;
+ if (fieldToParameterMap.ContainsKey(field)) {
+ expr.Operand = fieldToParameterMap[field];
+ } else {
+ expr.Operand = fieldToLocalMap[field];
+ }
+ expr.Arguments.Clear();
+ }
+ break;
+ case ILCode.Stfld:
+ if (expr.Arguments[0].MatchThis()) {
+ expr.Code = ILCode.Stloc;
+ if (fieldToParameterMap.ContainsKey(field)) {
+ expr.Operand = fieldToParameterMap[field];
+ } else {
+ expr.Operand = fieldToLocalMap[field];
+ }
+ expr.Arguments.RemoveAt(0);
+ }
+ break;
+ case ILCode.Ldflda:
+ if (expr.Arguments[0].MatchThis()) {
+ expr.Code = ILCode.Ldloca;
+ if (fieldToParameterMap.ContainsKey(field)) {
+ expr.Operand = fieldToParameterMap[field];
+ } else {
+ expr.Operand = fieldToLocalMap[field];
+ }
+ expr.Arguments.Clear();
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ #endregion
+ }
+}