diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index df9cc1e78..0a26a0586 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -46,7 +46,8 @@ namespace ICSharpCode.Decompiler.CSharp readonly DecompilerSettings settings; List ilTransforms = new List { - new RemoveDeadVariableInit(), + //new RemoveDeadVariableInit(), + new SplitVariables(), new ControlFlowSimplification(), new ILInlining(), // temporary pass, just to make the ILAst easier to read while debugging loop detection new LoopDetection(), @@ -70,7 +71,7 @@ namespace ICSharpCode.Decompiler.CSharp new ReplaceMethodCallsWithOperators(), new IntroduceUnsafeModifier(), new AddCheckedBlocks(), - new DeclareVariables(), // should run after most transforms that modify statements + //new DeclareVariables(), // should run after most transforms that modify statements new ConvertConstructorCallIntoInitializer(), // must run after DeclareVariables new DecimalConstantTransform(), new IntroduceUsingDeclarations(), @@ -591,20 +592,6 @@ namespace ICSharpCode.Decompiler.CSharp } var statementBuilder = new StatementBuilder(decompilationContext, method); var body = statementBuilder.ConvertAsBlock(function.Body); - - // insert variables at start of body - Statement prevVarDecl = null; - foreach (var v in function.Variables) { - if (v.LoadCount == 0 && v.StoreCount == 0 && v.AddressCount == 0) - continue; - if (v.Kind == VariableKind.Local || v.Kind == VariableKind.StackSlot) { - var type = typeSystemAstBuilder.ConvertType(v.Type); - var varDecl = new VariableDeclarationStatement(type, v.Name); - varDecl.Variables.Single().AddAnnotation(v); - body.Statements.InsertAfter(prevVarDecl, varDecl); - prevVarDecl = varDecl; - } - } entityDecl.AddChild(body, Roles.Body); } diff --git a/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs b/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs index 3776edafb..7fb334d0c 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs @@ -187,7 +187,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis /// The bottom state. /// Must not be mutated. /// - readonly State bottomState; + State bottomState; /// /// Current state. @@ -207,12 +207,22 @@ namespace ICSharpCode.Decompiler.FlowAnalysis /// protected State currentStateOnException; + bool initialized; + /// - /// Creates a new DataFlowVisitor. + /// Initializes the DataFlowVisitor. + /// This method must be called once before any Visit()-methods can be called. + /// It must not be called more than once. /// /// The initial state at the entry point of the analysis. - protected DataFlowVisitor(State initialState) + /// + /// This is a method instead of a constructor because derived classes might need complex initialization + /// before they can construct the initial state. + /// + protected void Initialize(State initialState) { + Debug.Assert(!initialized); + initialized = true; this.state = initialState.Clone(); this.bottomState = initialState.Clone(); this.bottomState.ReplaceWithBottom(); @@ -228,6 +238,8 @@ namespace ICSharpCode.Decompiler.FlowAnalysis void DebugPoint(Dictionary debugDict, ILInstruction inst) { #if DEBUG + Debug.Assert(initialized, "Initialize() was not called"); + State previousOutputState; if (debugDict.TryGetValue(inst, out previousOutputState)) { Debug.Assert(previousOutputState.LessThanOrEqual(state)); @@ -444,7 +456,9 @@ namespace ICSharpCode.Decompiler.FlowAnalysis { State oldStateOnException = currentStateOnException; State newStateOnException; - if (!stateOnException.TryGetValue(inst, out newStateOnException)) { + if (stateOnException.TryGetValue(inst, out newStateOnException)) { + newStateOnException.JoinWith(state); + } else { newStateOnException = state.Clone(); stateOnException.Add(inst, newStateOnException); } diff --git a/ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs b/ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs index b938808ff..7de302656 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs @@ -104,10 +104,11 @@ namespace ICSharpCode.Decompiler.FlowAnalysis readonly ILVariableScope scope; readonly BitSet variablesWithUninitializedUsage; - public DefiniteAssignmentVisitor(ILVariableScope scope) : base(new State(scope.Variables.Count)) + public DefiniteAssignmentVisitor(ILVariableScope scope) { this.scope = scope; this.variablesWithUninitializedUsage = new BitSet(scope.Variables.Count); + Initialize(new State(scope.Variables.Count)); } public bool IsPotentiallyUsedUninitialized(ILVariable v) diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitions.cs b/ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitionsVisitor.cs similarity index 61% rename from ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitions.cs rename to ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitionsVisitor.cs index c4e8d6c6e..74ab60b03 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitions.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitionsVisitor.cs @@ -37,11 +37,17 @@ namespace ICSharpCode.Decompiler.FlowAnalysis /// Possible "definitions" that store to a variable are: /// * StLoc /// * TryCatchHandler (for the exception variable) - /// * ReachingDefinitions.UninitializedVariable for uninitialized variables. + /// * ReachingDefinitionsVisitor.UninitializedVariable for uninitialized variables. /// Note that we do not keep track of LdLoca/references/pointers. /// The analysis will likely be wrong/incomplete for variables with AddressCount != 0. + /// + /// Note: this class does not store the computed information, because doing so + /// would significantly increase the number of states we need to store. + /// The only way to get the computed information out of this class is to + /// derive from the class and override the Visit methods at the points of interest + /// (usually the load instructions). /// - public class ReachingDefinitions + class ReachingDefinitionsVisitor : DataFlowVisitor { #region State representation /// @@ -70,14 +76,33 @@ namespace ICSharpCode.Decompiler.FlowAnalysis /// which allows us to efficient clear out all stores that get overwritten by a new store. /// [DebuggerDisplay("{bits}")] - struct State : IDataFlowState + public struct State : IDataFlowState { /// - /// bit 0: This state's position is reachable from the entry point. - /// bit i+1: There is a code path from the entry point to this state's position - /// that passes through through allStores[i] and does not pass through another - /// store to allStores[i].Variable. + /// This bitset contains three different kinds of bits: + /// Reachable bit: (bit 0) + /// This state's position is reachable from the entry point. + /// + /// Reaching uninitialized variable bit: (bit si, where si > 0 and allStores[si] == null) + /// There is a code path from the scope's entry point to this state's position + /// that does not pass through any store to the variable. + /// + /// firstStoreIndexForVariable[v.IndexInScope] gives the index of that variable's uninitialized bit. + /// + /// Reaching store bit (bit si, where allStores[si] != null): + /// There is a code path from the entry point to this state's position + /// that passes through through allStores[i] and does not pass through another + /// store to allStores[i].Variable. + /// + /// The indices for a variable's reaching store bits are between firstStoreIndexForVariable[v.IndexInScope] + /// to firstStoreIndexForVariable[v.IndexInScope + 1] (both endpoints exclusive!). /// + /// + /// The initial state has the "reachable bit" and the "reaching uninitialized variable bits" set, + /// and the "reaching store bits" unset. + /// + /// The bottom state has all bits unset. + /// readonly BitSet bits; public State(BitSet bits) @@ -102,11 +127,17 @@ namespace ICSharpCode.Decompiler.FlowAnalysis public void JoinWith(State incomingState) { + // When control flow is joined together, we can simply union our bitsets. + // (joined node is reachable iff either input is reachable) bits.UnionWith(incomingState.bits); } public void MeetWith(State incomingState) { + // At the end of a try-finally construct, we intersect the try-bitset + // with the finally-bitset + // (the try-finally-endpoint is reachable if both the try-block-endpoint and + // the finally-block-endpoint are reachable) bits.IntersectWith(incomingState.bits); } @@ -125,6 +156,9 @@ namespace ICSharpCode.Decompiler.FlowAnalysis get { return bits[ReachableBit]; } } + /// + /// Clears all store bits between startStoreIndex (incl.) and endStoreIndex (excl.) + /// public void KillStores(int startStoreIndex, int endStoreIndex) { Debug.Assert(startStoreIndex >= FirstStoreIndex); @@ -132,6 +166,11 @@ namespace ICSharpCode.Decompiler.FlowAnalysis bits.Clear(startStoreIndex, endStoreIndex); } + public bool IsReachingStore(int storeIndex) + { + return bits[storeIndex]; + } + public void SetStore(int storeIndex) { Debug.Assert(storeIndex >= FirstStoreIndex); @@ -152,16 +191,10 @@ namespace ICSharpCode.Decompiler.FlowAnalysis #endregion #region Documentation + member fields - /// - /// A special Nop instruction that gets used as a fake store if the variable - /// is possibly uninitialized. - /// - public static readonly ILInstruction UninitializedVariable = new Nop(); - /// /// The function being analyzed. /// - readonly ILVariableScope scope; + protected readonly ILVariableScope scope; /// /// All stores for all variables in the scope. @@ -190,16 +223,19 @@ namespace ICSharpCode.Decompiler.FlowAnalysis readonly int[] firstStoreIndexForVariable; /// - /// activeVariable[v.IndexInScope] is true iff RD analysis is enabled for the variable. + /// analyzedVariables[v.IndexInScope] is true iff RD analysis is enabled for the variable. /// - readonly BitSet activeVariables; + readonly BitSet analyzedVariables; #endregion #region Constructor /// - /// Run reaching definitions analysis for the specified variable scope. + /// Prepare reaching definitions analysis for the specified variable scope. + /// + /// The analysis will track all variables in the scope for which the predicate returns true + /// ("analyzed variables"). /// - public ReachingDefinitions(ILVariableScope scope, Predicate pred) + public ReachingDefinitionsVisitor(ILVariableScope scope, Predicate pred) : this(scope, GetActiveVariableBitSet(scope, pred)) { } @@ -216,19 +252,21 @@ namespace ICSharpCode.Decompiler.FlowAnalysis } /// - /// Run reaching definitions analysis for the specified variable scope. + /// Prepare reaching definitions analysis for the specified variable scope. + /// + /// The analysis will track all variables in the scope for which analyzedVariables[v.IndexInScope] is true. /// - public ReachingDefinitions(ILVariableScope scope, BitSet activeVariables) + public ReachingDefinitionsVisitor(ILVariableScope scope, BitSet analyzedVariables) { if (scope == null) throw new ArgumentNullException("scope"); - if (activeVariables == null) - throw new ArgumentNullException("activeVariables"); + if (analyzedVariables == null) + throw new ArgumentNullException("analyzedVariables"); this.scope = scope; - this.activeVariables = activeVariables; + this.analyzedVariables = analyzedVariables; // Fill `allStores` and `storeIndexMap` and `firstStoreIndexForVariable`. - var storesByVar = FindAllStoresByVariable(scope, activeVariables); + var storesByVar = FindAllStoresByVariable(scope, analyzedVariables); allStores = new ILInstruction[FirstStoreIndex + storesByVar.Sum(l => l != null ? l.Count : 0)]; firstStoreIndexForVariable = new int[scope.Variables.Count + 1]; int si = FirstStoreIndex; @@ -238,14 +276,15 @@ namespace ICSharpCode.Decompiler.FlowAnalysis if (stores != null) { int expectedStoreCount = scope.Variables[vi].StoreCount; if (!scope.Variables[vi].HasInitialValue) { - // Extra store for UninitializedVariable + // Extra store for the uninitialized state. expectedStoreCount += 1; // Note that for variables with HasInitialValue=true, // this extra store is already accounted for in ILVariable.StoreCount. } Debug.Assert(stores.Count == expectedStoreCount); stores.CopyTo(allStores, si); - // Add all stores except for UninitializedVariable to storeIndexMap. + // Add all stores except for the first (representing the uninitialized state) + // to storeIndexMap. for (int i = 1; i < stores.Count; i++) { storeIndexMap.Add(stores[i], si + i); } @@ -255,8 +294,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis firstStoreIndexForVariable[scope.Variables.Count] = si; Debug.Assert(si == allStores.Length); - RDVisitor visitor = new RDVisitor(this); - scope.Children.Single().AcceptVisitor(visitor); + Initialize(CreateInitialState()); } /// @@ -268,7 +306,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis List[] storesByVar = new List[scope.Variables.Count]; for (int vi = 0; vi < storesByVar.Length; vi++) { if (activeVariables[vi]) - storesByVar[vi] = new List { UninitializedVariable }; + storesByVar[vi] = new List { null }; } foreach (var inst in scope.Descendants) { ILVariable v; @@ -280,19 +318,17 @@ namespace ICSharpCode.Decompiler.FlowAnalysis } return storesByVar; } - #endregion - #region CreateInitialState /// - /// Create the initial state (reachable + all variables uninitialized). + /// Create the initial state (reachable bit + uninit variable bits set, store bits unset). /// State CreateInitialState() { BitSet initialState = new BitSet(allStores.Length); initialState.Set(ReachableBit); for (int vi = 0; vi < scope.Variables.Count; vi++) { - if (activeVariables[vi]) { - Debug.Assert(allStores[firstStoreIndexForVariable[vi]] == UninitializedVariable); + if (analyzedVariables[vi]) { + Debug.Assert(allStores[firstStoreIndexForVariable[vi]] == null); initialState.Set(firstStoreIndexForVariable[vi]); } } @@ -300,47 +336,70 @@ namespace ICSharpCode.Decompiler.FlowAnalysis } #endregion + #region Analysis + void HandleStore(ILInstruction inst, ILVariable v) + { + if (v.Scope == scope && analyzedVariables[v.IndexInScope] && state.IsReachable) { + // Clear the set of stores for this variable: + state.KillStores(firstStoreIndexForVariable[v.IndexInScope], firstStoreIndexForVariable[v.IndexInScope + 1]); + // And replace it with this store: + int si = storeIndexMap[inst]; + state.SetStore(si); + + // We should call PropagateStateOnException() here because we changed the state. + // But that's equal to: currentStateOnException.UnionWith(state); + + // Because we're already guaranteed that state.LessThanOrEqual(currentStateOnException) + // when entering HandleStore(), all we really need to do to achieve what PropagateStateOnException() does + // is to add the single additional store to the exceptional state as well: + currentStateOnException.SetStore(si); + } + } + + protected internal override void VisitStLoc(StLoc inst) + { + base.VisitStLoc(inst); + HandleStore(inst, inst.Variable); + } + + protected override void BeginTryCatchHandler(TryCatchHandler inst) + { + base.BeginTryCatchHandler(inst); + HandleStore(inst, inst.Variable); + } + + public bool IsAnalyzedVariable(ILVariable v) + { + return v.Scope == scope && analyzedVariables[v.IndexInScope]; + } + /// - /// Visitor that traverses the ILInstruction tree. + /// Gets all stores to v that reach the specified state. + /// + /// Precondition: v is an analyzed variable. /// - class RDVisitor : DataFlowVisitor + protected IEnumerable GetStores(State state, ILVariable v) { - readonly ReachingDefinitions rd; - - internal RDVisitor(ReachingDefinitions rd) : base(rd.CreateInitialState()) - { - this.rd = rd; - } - - void HandleStore(ILInstruction inst, ILVariable v) - { - if (v.Scope == rd.scope && rd.activeVariables[v.IndexInScope] && state.IsReachable) { - // Clear the set of stores for this variable: - state.KillStores(rd.firstStoreIndexForVariable[v.IndexInScope], rd.firstStoreIndexForVariable[v.IndexInScope + 1]); - // And replace it with this store: - int si = rd.storeIndexMap[inst]; - state.SetStore(si); - - // We should call PropagateStateOnException() here because we changed the state. - // But that's equal to: currentStateOnException.UnionWith(state); - - // Because we're already guaranteed that state.LessThanOrEqual(currentStateOnException) - // when entering HandleStore(), all we really need to do to achieve what PropagateStateOnException() does - // is to add the single additional store to the exceptional state as well: - currentStateOnException.SetStore(si); + Debug.Assert(v.Scope == scope && analyzedVariables[v.IndexInScope]); + int endIndex = firstStoreIndexForVariable[v.IndexInScope + 1]; + for (int si = firstStoreIndexForVariable[v.IndexInScope] + 1; si < endIndex; si++) { + if (state.IsReachingStore(si)) { + Debug.Assert(((IInstructionWithVariableOperand)allStores[si]).Variable == v); + yield return allStores[si]; } } - - protected internal override void VisitStLoc(StLoc inst) - { - base.VisitStLoc(inst); - HandleStore(inst, inst.Variable); - } - - protected override void BeginTryCatchHandler(TryCatchHandler inst) - { - HandleStore(inst, inst.Variable); - } } + + /// + /// Gets whether v is potentially uninitialized in the specified state. + /// + /// Precondition: v is an analyzed variable. + /// + protected bool IsPotentiallyUninitialized(State state, ILVariable v) + { + Debug.Assert(v.Scope == scope && analyzedVariables[v.IndexInScope]); + return state.IsReachingStore(firstStoreIndexForVariable[v.IndexInScope]); + } + #endregion } } diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 2acb3096f..b71441801 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -87,7 +87,7 @@ - + @@ -128,6 +128,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/ILVariable.cs b/ICSharpCode.Decompiler/IL/ILVariable.cs index bf7428520..14b52cad3 100644 --- a/ICSharpCode.Decompiler/IL/ILVariable.cs +++ b/ICSharpCode.Decompiler/IL/ILVariable.cs @@ -217,4 +217,9 @@ namespace ICSharpCode.Decompiler.IL output.WriteReference(this.Name, this, isLocal: true); } } + + public interface IInstructionWithVariableOperand + { + ILVariable Variable { get; set; } + } } diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index d53fea47d..ec1aad69a 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -963,7 +963,7 @@ namespace ICSharpCode.Decompiler.IL } /// Catch handler within a try-catch statement. - public sealed partial class TryCatchHandler : ILInstruction + public sealed partial class TryCatchHandler : ILInstruction, IInstructionWithVariableOperand { public TryCatchHandler(ILInstruction filter, ILInstruction body, ILVariable variable) : base(OpCode.TryCatchHandler) { @@ -1036,9 +1036,6 @@ namespace ICSharpCode.Decompiler.IL clone.Body = this.body.Clone(); return clone; } - readonly ILVariable variable; - /// Returns the variable operand. - public ILVariable Variable { get { return variable; } } public override void AcceptVisitor(ILVisitor visitor) { visitor.VisitTryCatchHandler(this); @@ -1192,16 +1189,13 @@ namespace ICSharpCode.Decompiler.IL } /// Loads the value of a local variable. (ldarg/ldloc) - public sealed partial class LdLoc : SimpleInstruction + public sealed partial class LdLoc : SimpleInstruction, IInstructionWithVariableOperand { public LdLoc(ILVariable variable) : base(OpCode.LdLoc) { Debug.Assert(variable != null); this.variable = variable; } - readonly ILVariable variable; - /// Returns the variable operand. - public ILVariable Variable { get { return variable; } } public override StackType ResultType { get { return variable.StackType; } } public override void WriteTo(ITextOutput output) { @@ -1220,7 +1214,7 @@ namespace ICSharpCode.Decompiler.IL } /// Loads the address of a local variable. (ldarga/ldloca) - public sealed partial class LdLoca : SimpleInstruction + public sealed partial class LdLoca : SimpleInstruction, IInstructionWithVariableOperand { public LdLoca(ILVariable variable) : base(OpCode.LdLoca) { @@ -1228,9 +1222,6 @@ namespace ICSharpCode.Decompiler.IL this.variable = variable; } public override StackType ResultType { get { return StackType.Ref; } } - readonly ILVariable variable; - /// Returns the variable operand. - public ILVariable Variable { get { return variable; } } public override void WriteTo(ITextOutput output) { output.Write(OpCode); @@ -1248,7 +1239,7 @@ namespace ICSharpCode.Decompiler.IL } /// Stores a value into a local variable. (starg/stloc) - public sealed partial class StLoc : ILInstruction + public sealed partial class StLoc : ILInstruction, IInstructionWithVariableOperand { public StLoc(ILVariable variable, ILInstruction value) : base(OpCode.StLoc) { @@ -1256,9 +1247,6 @@ namespace ICSharpCode.Decompiler.IL this.variable = variable; this.Value = value; } - readonly ILVariable variable; - /// Returns the variable operand. - public ILVariable Variable { get { return variable; } } public static readonly SlotInfo ValueSlot = new SlotInfo("Value", canInlineInto: true); ILInstruction value; public ILInstruction Value { diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index 906fe30a5..a65dfc9fc 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -774,15 +774,13 @@ namespace ICSharpCode.Decompiler.IL // HasVariableOperand trait: the instruction refers to a local variable static Action HasVariableOperand = opCode => { opCode.ConstructorParameters.Add("ILVariable variable"); - opCode.Members.Add("readonly ILVariable variable;"); opCode.ConstructorBody.Add("Debug.Assert(variable != null);"); opCode.ConstructorBody.Add("this.variable = variable;"); opCode.MatchParameters.Add(new MatchParamInfo { TypeName = "ILVariable", Name = "variable", FieldName = "Variable" }); - opCode.Members.Add("/// Returns the variable operand." + Environment.NewLine - + "public ILVariable Variable { get { return variable; } }"); opCode.GenerateWriteTo = true; opCode.WriteOperand.Add("output.Write(' ');"); opCode.WriteOperand.Add("variable.WriteTo(output);"); + opCode.Interfaces.Add("IInstructionWithVariableOperand"); }; static Action HasFieldOperand = opCode => { diff --git a/ICSharpCode.Decompiler/IL/Instructions/LocalVarInstructions.cs b/ICSharpCode.Decompiler/IL/Instructions/LocalVarInstructions.cs index b6bbafd2a..957d74583 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/LocalVarInstructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/LocalVarInstructions.cs @@ -23,6 +23,20 @@ namespace ICSharpCode.Decompiler.IL { partial class LdLoc { + ILVariable variable; + + public ILVariable Variable { + get { return variable; } + set { + Debug.Assert(value != null); + if (IsConnected) + variable.LoadCount--; + variable = value; + if (IsConnected) + variable.LoadCount++; + } + } + protected override void Connected() { base.Connected(); @@ -44,6 +58,20 @@ namespace ICSharpCode.Decompiler.IL partial class LdLoca { + ILVariable variable; + + public ILVariable Variable { + get { return variable; } + set { + Debug.Assert(value != null); + if (IsConnected) + variable.AddressCount--; + variable = value; + if (IsConnected) + variable.AddressCount++; + } + } + protected override void Connected() { base.Connected(); @@ -65,6 +93,20 @@ namespace ICSharpCode.Decompiler.IL partial class StLoc { + ILVariable variable; + + public ILVariable Variable { + get { return variable; } + set { + Debug.Assert(value != null); + if (IsConnected) + variable.StoreCount--; + variable = value; + if (IsConnected) + variable.StoreCount++; + } + } + protected override void Connected() { base.Connected(); diff --git a/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs index 1b53e108e..bfc807d67 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs @@ -175,6 +175,20 @@ namespace ICSharpCode.Decompiler.IL body.WriteTo(output); } + ILVariable variable; + + public ILVariable Variable { + get { return variable; } + set { + Debug.Assert(value != null); + if (IsConnected) + variable.StoreCount--; + variable = value; + if (IsConnected) + variable.StoreCount++; + } + } + protected override void Connected() { base.Connected(); diff --git a/ICSharpCode.Decompiler/IL/Instructions/VariableScope.cs b/ICSharpCode.Decompiler/IL/Instructions/VariableScope.cs index e4f107bc0..34f88f97e 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/VariableScope.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/VariableScope.cs @@ -123,7 +123,8 @@ namespace ICSharpCode.Decompiler.IL { for (int i = 0; i < list.Count;) { var v = list[i]; - if (v.StoreCount == 0 && v.LoadCount == 0 && v.AddressCount == 0) { + int deadStoreCount = v.HasInitialValue ? 1 : 0; + if (v.StoreCount == deadStoreCount && v.LoadCount == 0 && v.AddressCount == 0) { RemoveAt(i); } else { i++; diff --git a/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs new file mode 100644 index 000000000..b9c9ed6c7 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs @@ -0,0 +1,111 @@ +// Copyright (c) 2016 Daniel Grunwald +// +// 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; + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + /// + /// Live range splitting for IL variables. + /// + public class SplitVariables : IILTransform + { + public void Run(ILFunction function, ILTransformContext context) + { + var groupStores = new GroupStores(function); + function.Body.AcceptVisitor(groupStores); + var newVariables = new Dictionary(); + // Replace analyzed variables with their split versions: + foreach (var inst in function.Descendants.OfType()) { + if (groupStores.IsAnalyzedVariable(inst.Variable)) { + inst.Variable = groupStores.GetNewVariable(inst); + } + } + function.Variables.RemoveDead(); + } + + static bool IsCandidateVariable(ILVariable v) + { + switch (v.Kind) { + case VariableKind.Local: + case VariableKind.Exception: + return v.AddressCount == 0; + default: + // parameters: avoid splitting parameters + // stack slots: are already split by construction + // pinned locals: not sure if splitting those would be legal + return false; + } + } + + /// + /// Use the union-find structure to merge + /// + /// + /// Instructions in a group are stores to the same variable that must stay together (cannot be split). + /// + class GroupStores : ReachingDefinitionsVisitor + { + readonly UnionFind unionFind = new UnionFind(); + readonly HashSet uninitVariableUsage = new HashSet(); + + public GroupStores(ILVariableScope scope) : base(scope, IsCandidateVariable) + { + } + + protected internal override void VisitLdLoc(LdLoc inst) + { + base.VisitLdLoc(inst); + if (IsAnalyzedVariable(inst.Variable)) { + if (IsPotentiallyUninitialized(state, inst.Variable)) { + uninitVariableUsage.Add(inst); + } + foreach (var store in GetStores(state, inst.Variable)) { + unionFind.Merge(inst, (IInstructionWithVariableOperand)store); + } + } + } + + readonly Dictionary newVariables = new Dictionary(); + + /// + /// Gets the new variable for a LdLoc, StLoc or TryCatchHandler instruction. + /// + internal ILVariable GetNewVariable(IInstructionWithVariableOperand inst) + { + var representative = unionFind.Find(inst); + ILVariable v; + if (!newVariables.TryGetValue(representative, out v)) { + v = new ILVariable(inst.Variable.Kind, inst.Variable.Type, inst.Variable.StackType, inst.Variable.Index); + v.Name = inst.Variable.Name; + v.HasInitialValue = false; // we'll set HasInitialValue when we encounter an uninit load + newVariables.Add(representative, v); + inst.Variable.Scope.Variables.Add(v); + } + if (uninitVariableUsage.Contains(inst)) { + v.HasInitialValue = true; + } + return v; + } + } + } +} diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs index 089bfcbe9..1f7e81553 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs @@ -22,7 +22,7 @@ using System.Linq; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; -namespace ICSharpCode.Decompiler.IL +namespace ICSharpCode.Decompiler.IL.Transforms { public class TransformArrayInitializers : IILTransform {