diff --git a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs index a8f142969..ea228e014 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs @@ -12,8 +12,8 @@ namespace Decompiler { class StackSlot { - public List PushedBy; // Pushed by one of these; null element means exception pushed by CLR - public ILVariable LoadFrom; + public List PushedBy; // One of those + public ILVariable LoadFrom; // Where can we get the value from in AST public StackSlot() { @@ -24,41 +24,93 @@ namespace Decompiler this.PushedBy = new List(1); this.PushedBy.Add(pushedBy); } + + public static List CloneStack(List stack, int? popCount) + { + List clone = new List(); + if (popCount.HasValue) { + if (popCount.Value > stack.Count) { + throw new Exception("Can not pop - the stack is empty"); + } + for(int i = 0; i < stack.Count - popCount.Value; i++) { + clone.Add(new StackSlot() { PushedBy = new List(stack[i].PushedBy) }); + } + } + return clone; + } + } + + class VariableSlot + { + public static List Empty = new List(); + + public List StoredBy = Empty; // One of those + public bool StoredByAll; // Overestimate which is useful for exceptional control flow. + + public static VariableSlot[] CloneVariableState(VariableSlot[] state) + { + VariableSlot[] clone = new ILAstBuilder.VariableSlot[state.Length]; + if (VariableSlot.Empty.Count > 0) + throw new Exception("Constant data corrupted"); + for (int i = 0; i < clone.Length; i++) { + VariableSlot varSlot = state[i]; + clone[i] = new VariableSlot() { + StoredBy = varSlot.StoredBy.Count == 0 ? VariableSlot.Empty : new List(varSlot.StoredBy), + StoredByAll = varSlot.StoredByAll + }; + } + return clone; + } + + public static VariableSlot[] MakeEmptyState(int varCount) + { + VariableSlot[] emptyVariableState = new VariableSlot[varCount]; + for (int i = 0; i < emptyVariableState.Length; i++) { + emptyVariableState[i] = new VariableSlot(); + } + return emptyVariableState; + } + + public static VariableSlot[] MakeFullState(int varCount) + { + VariableSlot[] unknownVariableState = new VariableSlot[varCount]; + for (int i = 0; i < unknownVariableState.Length; i++) { + unknownVariableState[i] = new VariableSlot() { StoredByAll = true }; + } + return unknownVariableState; + } } class ByteCode { - public ILLabel Label; // Non-null only if needed + 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? PopCount; // Null means pop all public int PushCount; public string Name { get { return "IL_" + this.Offset.ToString("X2"); } } public ByteCode Next; - public Instruction[] Prefixes; // Non-null only if needed - public List StackBefore; - public List StoreTo; + public Instruction[] Prefixes; // Non-null only if needed + public List StackBefore; + public List StoreTo; // Store result of instruction to those AST variables + public VariableSlot[] VariablesBefore; - public List CloneStack(int? popCount) - { - List clone = new List(); - if (popCount.HasValue) { - if (popCount.Value > this.StackBefore.Count) { - throw new Exception("Can not pop - the stack is empty"); - } - for(int i = 0; i < this.StackBefore.Count - popCount.Value; i++) { - clone.Add(new StackSlot() { PushedBy = new List(this.StackBefore[i].PushedBy) }); - } - } - return clone; - } + public VariableDefinition OperandAsVariable { get { return (VariableDefinition)this.Operand; } } public override string ToString() { StringBuilder sb = new StringBuilder(); - sb.AppendFormat("{0}:{1} ", this.Name, this.Label != null ? " *" : ""); + + // Label + sb.Append(this.Name); + sb.Append(':'); + if (this.Label != null) + sb.Append('*'); + + // Name + sb.Append(' '); if (this.Prefixes != null) { foreach (var prefix in this.Prefixes) { sb.Append(prefix.OpCode.Name); @@ -66,18 +118,30 @@ namespace Decompiler } } sb.Append(this.Code.GetName()); - if (this.Operand is ILLabel) { - sb.Append(((ILLabel)this.Operand).Name); - } else if (this.Operand is ILLabel[]) { - foreach(ILLabel label in (ILLabel[])this.Operand) { - sb.Append(label.Name); - sb.Append(" "); + + if (this.Operand != null) { + sb.Append(' '); + if (this.Operand is Instruction) { + sb.Append("IL_" + ((Instruction)this.Operand).Offset.ToString("X2")); + } else if (this.Operand is Instruction[]) { + foreach(Instruction inst in (Instruction[])this.Operand) { + sb.Append("IL_" + inst.Offset.ToString("X2")); + sb.Append(" "); + } + } else if (this.Operand is ILLabel) { + sb.Append(((ILLabel)this.Operand).Name); + } else if (this.Operand is ILLabel[]) { + foreach(ILLabel label in (ILLabel[])this.Operand) { + sb.Append(label.Name); + sb.Append(" "); + } + } else { + sb.Append(this.Operand.ToString()); } - } else { - sb.Append(this.Operand.ToString()); } + if (this.StackBefore != null) { - sb.Append(" StackBefore = {"); + sb.Append(" StackBefore={"); bool first = true; foreach (StackSlot slot in this.StackBefore) { if (!first) sb.Append(","); @@ -91,8 +155,9 @@ namespace Decompiler } sb.Append("}"); } + if (this.StoreTo != null && this.StoreTo.Count > 0) { - sb.Append(" StoreTo = {"); + sb.Append(" StoreTo={"); bool first = true; foreach (ILVariable stackVar in this.StoreTo) { if (!first) sb.Append(","); @@ -101,6 +166,29 @@ namespace Decompiler } sb.Append("}"); } + + if (this.VariablesBefore != null) { + sb.Append(" VarsBefore={"); + bool first = true; + foreach (VariableSlot varSlot in this.VariablesBefore) { + if (!first) sb.Append(","); + if (varSlot.StoredByAll) { + sb.Append("*"); + } else if (varSlot.StoredBy.Count == 0) { + sb.Append("_"); + } else { + bool first2 = true; + foreach (ByteCode storedBy in varSlot.StoredBy) { + if (!first2) sb.Append("|"); + sb.AppendFormat("IL_{0:X2}", storedBy.Offset); + first2 = false; + } + } + first = false; + } + sb.Append("}"); + } + return sb.ToString(); } } @@ -167,18 +255,13 @@ namespace Decompiler body[i].Next = body[i + 1]; } - Queue agenda = new Queue(); + Stack agenda = new Stack(); - // Add known states - body[0].StackBefore = new List(); - agenda.Enqueue(body[0]); + int varCount = methodDef.Body.Variables.Count; + // Add known states if(methodDef.Body.HasExceptionHandlers) { foreach(ExceptionHandler ex in methodDef.Body.ExceptionHandlers) { - ByteCode tryStart = instrToByteCode[ex.TryStart]; - tryStart.StackBefore = new List(); - agenda.Enqueue(tryStart); - ByteCode handlerStart = instrToByteCode[ex.HandlerType == ExceptionHandlerType.Filter ? ex.FilterStart : ex.HandlerStart]; handlerStart.StackBefore = new List(); if (ex.HandlerType == ExceptionHandlerType.Catch || ex.HandlerType == ExceptionHandlerType.Filter) { @@ -191,27 +274,39 @@ namespace Decompiler ldexceptions[ex] = ldexception; handlerStart.StackBefore.Add(new StackSlot(ldexception)); } - agenda.Enqueue(handlerStart); - - // Control flow is not required to reach endfilter - if (ex.HandlerType == ExceptionHandlerType.Filter) { - ByteCode endFilter = instrToByteCode[ex.FilterEnd.Previous]; - endFilter.StackBefore = new List(); - } + handlerStart.VariablesBefore = VariableSlot.MakeFullState(varCount); + agenda.Push(handlerStart); } } + body[0].StackBefore = new List(); + body[0].VariablesBefore = VariableSlot.MakeEmptyState(varCount); + agenda.Push(body[0]); + // Process agenda while(agenda.Count > 0) { - ByteCode byteCode = agenda.Dequeue(); + ByteCode byteCode = agenda.Pop(); // Calculate new stack - List newStack = byteCode.CloneStack(byteCode.PopCount); + List newStack = StackSlot.CloneStack(byteCode.StackBefore, byteCode.PopCount); for (int i = 0; i < byteCode.PushCount; i++) { newStack.Add(new StackSlot(byteCode)); } - // Apply the state to any successors + // Calculate new variable state + VariableSlot[] newVariableState = VariableSlot.CloneVariableState(byteCode.VariablesBefore); + if (byteCode.Code == ILCode.Stloc) { + int varIndex = ((VariableReference)byteCode.Operand).Index; + newVariableState[varIndex].StoredBy = new List(1) { byteCode }; + newVariableState[varIndex].StoredByAll = false; + } + + // After the leave, finally block might have touched the variables + if (byteCode.Code == ILCode.Leave) { + newVariableState = VariableSlot.MakeFullState(varCount); + } + + // Find all successors List branchTargets = new List(); if (byteCode.Code.CanFallThough()) { branchTargets.Add(byteCode.Next); @@ -233,21 +328,30 @@ namespace Decompiler target.Label = new ILLabel() { Name = target.Name }; } } + + // Apply the state to successors foreach (ByteCode branchTarget in branchTargets) { - if (branchTarget.StackBefore == null) { - branchTarget.StackBefore = newStack; - // Do not share one stack for several bytecodes - if (branchTargets.Count > 1) { - branchTarget.StackBefore = branchTarget.CloneStack(0); + 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.CloneStack(newStack, 0); + branchTarget.VariablesBefore = VariableSlot.CloneVariableState(newVariableState); } - agenda.Enqueue(branchTarget); + agenda.Push(branchTarget); } else { if (branchTarget.StackBefore.Count != newStack.Count) { throw new Exception("Inconsistent stack size at " + byteCode.Name); } - // Merge stacks + // 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.Count; i++) { List oldPushedBy = branchTarget.StackBefore[i].PushedBy; List newPushedBy = oldPushedBy.Union(newStack[i].PushedBy).ToList(); @@ -257,8 +361,28 @@ namespace Decompiler } } + // Merge variables - modify the target + for (int i = 0; i < newVariableState.Length; i++) { + VariableSlot oldSlot = branchTarget.VariablesBefore[i]; + VariableSlot newSlot = newVariableState[i]; + // All can not be unioned further + if (!oldSlot.StoredByAll) { + if (newSlot.StoredByAll) { + oldSlot.StoredByAll = true; + modified = true; + } else { + List oldStoredBy = oldSlot.StoredBy; + List newStoredBy = oldStoredBy.Union(newSlot.StoredBy).ToList(); + if (newStoredBy.Count > oldStoredBy.Count) { + oldSlot.StoredBy = newStoredBy; + modified = true; + } + } + } + } + if (modified) { - agenda.Enqueue(branchTarget); + agenda.Push(branchTarget); } } } @@ -288,36 +412,8 @@ namespace Decompiler } } - // Convert local varibles - Variables = methodDef.Body.Variables.Select(v => new ILVariable() { Name = string.IsNullOrEmpty(v.Name) ? "var_" + v.Index : v.Name, Type = v.VariableType }).ToList(); - int[] numReads = new int[Variables.Count]; - int[] numWrites = new int[Variables.Count]; - foreach(ByteCode byteCode in body) { - if (byteCode.Code == ILCode.Ldloc) { - int index = ((VariableDefinition)byteCode.Operand).Index; - byteCode.Operand = Variables[index]; - numReads[index]++; - } else if (byteCode.Code == ILCode.Stloc) { - int index = ((VariableDefinition)byteCode.Operand).Index; - byteCode.Operand = Variables[index]; - numWrites[index]++; - } else if (byteCode.Code == ILCode.Ldloca) { - int index = ((VariableDefinition)byteCode.Operand).Index; - byteCode.Operand = Variables[index]; - // ldloca leads to an unknown numbers of reads/writes, so ensure we don't inline the variable - numReads[index] += 2; - numWrites[index] += 2; - } - } - - // Find which variables we can inline - if (this.optimize) { - for (int i = 0; i < Variables.Count; i++) { - if (numReads[i] == 1 && numWrites[i] == 1) { - allowInline[Variables[i]] = true; - } - } - } + // Split and convert the normal local variables + ConvertLocalVariables(body); // Convert branch targets to labels foreach(ByteCode byteCode in body) { @@ -335,6 +431,108 @@ namespace Decompiler return body; } + class VariableInfo + { + public ILVariable Variable; + public List Stores; + public List Loads; + } + + /// + /// If possible, separates local variables into several independent variables. + /// It should undo any compilers merging. + /// + void ConvertLocalVariables(List body) + { + if (optimize) { + int varCount = methodDef.Body.Variables.Count; + this.Variables = new List(varCount * 2); + + for(int variableIndex = 0; variableIndex < varCount; variableIndex++) { + // Find all stores and loads for this variable + List stores = body.Where(b => b.Code == ILCode.Stloc && b.Operand is VariableDefinition && b.OperandAsVariable.Index == variableIndex).ToList(); + List loads = body.Where(b => (b.Code == ILCode.Ldloc || b.Code == ILCode.Ldloca) && b.Operand is VariableDefinition && b.OperandAsVariable.Index == variableIndex).ToList(); + TypeReference varType = methodDef.Body.Variables[variableIndex].VariableType; + + List newVars; + + // If any of the loads is from "all", use single variable + // If any of the loads is ldloca, fallback to single variable as well + if (loads.Any(b => b.VariablesBefore[variableIndex].StoredByAll || b.Code == ILCode.Ldloca)) { + newVars = new List(1) { new VariableInfo() { + Variable = new ILVariable() { + Name = "var_" + variableIndex, + Type = varType, + OriginalVariable = methodDef.Body.Variables[variableIndex] + }, + Stores = stores, + Loads = loads + }}; + } else { + // Create a new variable for each store + newVars = stores.Select(st => new VariableInfo() { + Variable = new ILVariable() { + Name = "var_" + variableIndex + "_" + st.Offset.ToString("X2"), + Type = varType, + OriginalVariable = methodDef.Body.Variables[variableIndex] + }, + Stores = new List() {st}, + Loads = new List() + }).ToList(); + + // Add loads to the data structure; merge variables if necessary + foreach(ByteCode load in loads) { + List storedBy = load.VariablesBefore[variableIndex].StoredBy; + if (storedBy.Count == 0) { + throw new Exception("Load of uninitialized variable"); + } else if (storedBy.Count == 1) { + VariableInfo newVar = newVars.Where(v => v.Stores.Contains(storedBy[0])).Single(); + newVar.Loads.Add(load); + } else { + List mergeVars = newVars.Where(v => v.Stores.Union(storedBy).Any()).ToList(); + VariableInfo mergedVar = new VariableInfo() { + Variable = mergeVars[0].Variable, + Stores = mergeVars.SelectMany(v => v.Stores).ToList(), + Loads = mergeVars.SelectMany(v => v.Loads).ToList() + }; + mergedVar.Loads.Add(load); + newVars = newVars.Except(mergeVars).ToList(); + newVars.Add(mergedVar); + } + } + + // Permit inlining + foreach(VariableInfo newVar in newVars) { + if (newVar.Stores.Count == 1 && newVar.Loads.Count == 1) { + allowInline[newVar.Variable] = true; + } + } + } + + // Set bytecode operands + foreach(VariableInfo newVar in newVars) { + foreach(ByteCode store in newVar.Stores) { + store.Operand = newVar.Variable; + } + foreach(ByteCode load in newVar.Loads) { + load.Operand = newVar.Variable; + } + } + + // Record new variables to global list + this.Variables.AddRange(newVars.Select(v => v.Variable)); + } + } else { + this.Variables = methodDef.Body.Variables.Select(v => new ILVariable() { Name = string.IsNullOrEmpty(v.Name) ? "var_" + v.Index : v.Name, Type = v.VariableType, OriginalVariable = v }).ToList(); + foreach(ByteCode byteCode in body) { + if (byteCode.Code == ILCode.Ldloc || byteCode.Code == ILCode.Stloc || byteCode.Code == ILCode.Ldloca) { + int index = ((VariableDefinition)byteCode.Operand).Index; + byteCode.Operand = this.Variables[index]; + } + } + } + } + List ConvertToAst(List body, HashSet ehs) { List ast = new List(); @@ -490,6 +688,7 @@ namespace Decompiler // We are moving the expression evaluation past the other aguments. // It is ok to pass ldloc because the expression can not contain stloc and thus the ldcoc will still return the same value + // Do not inline ldloca if (arg.Code == ILCode.Ldloc) { if (arg.Operand == currExpr.Operand) { bool canInline; @@ -500,6 +699,9 @@ namespace Decompiler currExpr.Arguments[0].ILRanges.AddRange(currExpr.ILRanges); currExpr.Arguments[0].ILRanges.AddRange(nextExpr.Arguments[j].ILRanges); + // Remove from global list, if present + this.Variables.Remove((ILVariable)arg.Operand); + ast.RemoveAt(i); nextExpr.Arguments[j] = currExpr.Arguments[0]; // Inline the stloc body i -= 2; // Try the same index again diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index ece707daf..1d0e2785a 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -194,6 +194,9 @@ namespace Decompiler.ControlFlow { List result = new List(); + // Do not modify entry data + scope = new HashSet(scope); + Queue agenda = new Queue(); agenda.Enqueue(entryPoint); while(agenda.Count > 0) { @@ -241,6 +244,7 @@ namespace Decompiler.ControlFlow foreach(var node in scope) { result.Add((ILNode)node.UserData); } + scope.Clear(); return result; } @@ -259,6 +263,9 @@ namespace Decompiler.ControlFlow { List result = new List(); + // Do not modify entry data + scope = new HashSet(scope); + HashSet agenda = new HashSet(); agenda.Add(entryNode); while(agenda.Any()) { diff --git a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs index 82c05d985..92ea2f680 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstTypes.cs @@ -30,7 +30,7 @@ namespace Decompiler { StringWriter w = new StringWriter(); WriteTo(new PlainTextOutput(w)); - return w.ToString(); + return w.ToString().Replace("\r\n", "; "); } public abstract void WriteTo(ITextOutput output); @@ -176,6 +176,7 @@ namespace Decompiler public string Name; public bool IsGenerated; public TypeReference Type; + public VariableDefinition OriginalVariable; public override string ToString() {