Browse Source

Extract reusable DataFlowVisitor from ReachingDefinitions; and add ILVariable.HasInitialValue.

pull/728/head
Daniel Grunwald 9 years ago
parent
commit
f5e66ff623
  1. 2
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  2. 2
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 418
      ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
  4. 171
      ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs
  5. 417
      ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitions.cs
  6. 4
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  7. 23
      ICSharpCode.Decompiler/IL/ILReader.cs
  8. 85
      ICSharpCode.Decompiler/IL/ILVariable.cs
  9. 4
      ICSharpCode.Decompiler/IL/InstructionFlags.cs
  10. 2
      ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
  11. 2
      ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
  12. 6
      ICSharpCode.Decompiler/IL/SemanticHelper.cs
  13. 3
      ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs
  14. 16
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  15. 6
      ICSharpCode.Decompiler/IL/Transforms/InlineCompilerGeneratedVariables.cs
  16. 15
      ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs
  17. 22
      ICSharpCode.Decompiler/Util/BitSet.cs
  18. 4
      ILSpy/Languages/CSharpLanguage.cs

2
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -46,11 +46,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -46,11 +46,11 @@ namespace ICSharpCode.Decompiler.CSharp
readonly DecompilerSettings settings;
List<IILTransform> ilTransforms = new List<IILTransform> {
new RemoveDeadVariableInit(),
new ControlFlowSimplification(),
new ILInlining(), // temporary pass, just to make the ILAst easier to read while debugging loop detection
new LoopDetection(),
new IntroduceExitPoints(),
new SplitVariables(),
new ConditionDetection(),
new ILInlining(),
new CopyPropagation(),

2
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -105,7 +105,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -105,7 +105,7 @@ namespace ICSharpCode.Decompiler.CSharp
ExpressionWithResolveResult ConvertVariable(ILVariable variable)
{
Expression expr;
if (variable.Kind == VariableKind.This)
if (variable.Kind == VariableKind.Parameter && variable.Index < 0)
expr = new ThisReferenceExpression();
else
expr = new IdentifierExpression(variable.Name);

418
ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs

@ -0,0 +1,418 @@ @@ -0,0 +1,418 @@
// 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 ICSharpCode.Decompiler.IL;
namespace ICSharpCode.Decompiler.FlowAnalysis
{
/// <summary>
/// Interface for use with DataFlowVisitor.
///
/// A mutable container for the state tracked by the data flow analysis.
/// </summary>
/// <remarks>
/// To ensure the data flow analysis terminates, states must form a partially ordered set (poset)
/// with finite height.
/// </remarks>
public interface IDataFlowState<Self> where Self: IDataFlowState<Self>
{
/// <summary>
/// Gets whether this state is "less than" (or equal to) another state.
/// This is the partial order of the poset.
/// </summary>
/// <remarks>
/// The exact meaning of this relation is up to the concrete implementation,
/// but usually "less than" means "has less information than".
/// A given position in the code starts at the "unreachable state" (=no information)
/// and then adds more information as the analysis progresses.
/// After each change to the state, the old state must be less than the new state,
/// so that the analysis does not run into an infinite loop.
/// The poset must also have finite height (no infinite ascending chains s1 &lt; s2 &lt; ...),
/// to ensure the analysis terminates.
/// </remarks>
bool LessThanOrEqual(Self otherState);
/// <summary>
/// Creates a new object with a copy of the state.
/// </summary>
Self Clone();
/// <summary>
/// Replace the contents of this state object with a copy of those in <paramref name="newContent"/>.
/// </summary>
/// <remarks>
/// <c>x = x.Clone(); x.ReplaceWith(newContent);</c>
/// is equivalent to
/// <c>x = newContent.Clone();</c>
///
/// ReplaceWith() is used to avoid allocating new state objects where possible.
/// </remarks>
void ReplaceWith(Self newContent);
/// <summary>
/// Join the incomingState into this state.
/// </summary>
/// <remarks>
/// Postcondition: <c>old(this).LessThan(this) &amp;&amp; incomingState.LessThan(this)</c>
/// This method generally sets <c>this</c> to the smallest state that is greater than (or equal to)
/// both input states.
/// </remarks>
void JoinWith(Self incomingState);
/// <summary>
/// Gets whether this is the "unreachable" state.
/// The unreachable state represents that the data flow analysis has not yet
/// found a code path from the entry point to this state's position.
/// </summary>
/// <remarks>
/// The unreachable state is the bottom element in the poset: the unreachable state is "less than" all other states.
/// </remarks>
bool IsUnreachable { get; }
/// <summary>
/// Equivalent to <c>this.ReplaceWith(unreachableState)</c>, but may be more efficient.
/// </summary>
void MarkUnreachable();
}
/// <summary>
/// Generic base class for forward data flow analyses.
/// </summary>
/// <typeparam name="State">
/// The state type used for the data flow analysis. See <see cref="IDataFlowState{Self}"/> for details.
///
/// <c>DataFlowVisitor</c> expects the state to behave like a mutable reference type.
/// It might still be a good idea to use a struct to implement it so that .NET uses static dispatch for
/// method calls on the type parameter, but that struct must consist only of a <c>readonly</c> field
/// referencing some mutable object, to ensure the type parameter behaves as it if was a mutable reference type.
/// </typeparam>
public abstract class DataFlowVisitor<State> : ILVisitor
where State : IDataFlowState<State>
{
// The data flow analysis tracks a 'state'.
// There are many states (one per source code position, i.e. ILInstruction), but we don't store all of them.
// We only keep track of:
// a) the current state in the RDVisitor
// This state corresponds to the instruction currently being visited,
// and gets mutated as we traverse the ILAst.
// b) the input state for each control flow node
// This also gets mutated as the analysis learns about new control flow edges.
/// <summary>
/// The unreachable state.
/// Must not be mutated.
/// </summary>
readonly State unreachableState;
/// <summary>
/// Combined state of all possible exceptional control flow paths in the current try block.
/// Serves as input state for catch blocks.
/// </summary>
State stateOnException;
/// <summary>
/// Current state.
/// Gets mutated as the visitor traverses the ILAst.
/// </summary>
protected State state;
/// <summary>
/// Creates a new DataFlowVisitor.
/// </summary>
/// <param name="initialState">The initial state at the entry point of the analysis.</param>
protected DataFlowVisitor(State initialState)
{
this.state = initialState.Clone();
this.unreachableState = initialState.Clone();
this.unreachableState.MarkUnreachable();
Debug.Assert(unreachableState.IsUnreachable);
this.stateOnException = unreachableState.Clone();
}
protected sealed override void Default(ILInstruction inst)
{
// This method assumes normal control flow and no branches.
if ((inst.DirectFlags & (InstructionFlags.ControlFlow | InstructionFlags.MayBranch | InstructionFlags.EndPointUnreachable)) != 0) {
throw new NotImplementedException("RDVisitor is missing implementation for " + inst.GetType().Name);
}
// Since this instruction has normal control flow, we can evaluate our children left-to-right.
foreach (var child in inst.Children) {
child.AcceptVisitor(this);
Debug.Assert(state.IsUnreachable || !child.HasFlag(InstructionFlags.EndPointUnreachable));
}
// If this instruction can throw an exception, handle the exceptional control flow edge.
if ((inst.DirectFlags & InstructionFlags.MayThrow) != 0) {
MayThrow();
}
}
/// <summary>
/// Handle control flow when the current instruction throws an exception:
/// joins the current state into the "exception state" of the current try block.
/// </summary>
protected void MayThrow()
{
stateOnException.JoinWith(state);
}
/// <summary>
/// Holds the state for incoming branches.
/// </summary>
/// <remarks>
/// Only used for blocks in block containers; not for inline blocks.
/// </remarks>
readonly Dictionary<Block, State> stateOnBranch = new Dictionary<Block, State>();
/// <summary>
/// Holds the state at the block container end-point. (=state for incoming 'leave' instructions)
/// </summary>
readonly Dictionary<BlockContainer, State> stateOnLeave = new Dictionary<BlockContainer, State>();
State GetBlockInputState(Block block)
{
State s;
if (stateOnBranch.TryGetValue(block, out s)) {
return s;
} else {
s = unreachableState.Clone();
stateOnBranch.Add(block, s);
return s;
}
}
/// <summary>
/// For each block container, stores the set of blocks (via Block.ChildIndex)
/// that had their incoming state changed and were not processed yet.
/// </summary>
readonly Dictionary<BlockContainer, SortedSet<int>> workLists = new Dictionary<BlockContainer, SortedSet<int>>();
protected internal override void VisitBlockContainer(BlockContainer container)
{
SortedSet<int> worklist = new SortedSet<int>();
// register work list so that branches within this container can add to it
workLists.Add(container, worklist);
var stateOnEntry = GetBlockInputState(container.EntryPoint);
if (!state.LessThanOrEqual(stateOnEntry)) {
// If we have new information for the container's entry point,
// add the container entry point to the work list.
stateOnEntry.JoinWith(state);
worklist.Add(0);
}
// To handle loops, we need to analyze the loop body before we can know the state for the loop backedge,
// but we need to know the input state for the loop body (to which the backedge state contributes)
// before we can analyze the loop body.
// Solution: we repeat the analysis of the loop body multiple times, until the state no longer changes.
// To make it terminate reasonably quickly, we need to process the control flow nodes in the correct order:
// reverse post-order. We use a SortedSet<int> for this, and assume that the block indices used in the SortedSet
// are ordered appropriately. The caller can use BlockContainer.SortBlocks() for this.
while (worklist.Count > 0) {
int blockIndex = worklist.Min;
worklist.Remove(blockIndex);
Block block = container.Blocks[blockIndex];
state.ReplaceWith(stateOnBranch[block]);
block.AcceptVisitor(this);
}
State stateOnExit;
if (stateOnLeave.TryGetValue(container, out stateOnExit)) {
state.ReplaceWith(stateOnExit);
} else {
state.MarkUnreachable();
}
}
protected internal override void VisitBranch(Branch inst)
{
var targetBlock = inst.TargetBlock;
var targetState = GetBlockInputState(targetBlock);
if (!state.LessThanOrEqual(targetState)) {
targetState.JoinWith(state);
BlockContainer container = (BlockContainer)targetBlock.Parent;
workLists[container].Add(targetBlock.ChildIndex);
}
state.MarkUnreachable();
}
protected internal override void VisitLeave(Leave inst)
{
State targetState;
if (stateOnLeave.TryGetValue(inst.TargetContainer, out targetState)) {
targetState.JoinWith(state);
} else {
stateOnLeave.Add(inst.TargetContainer, state.Clone());
}
// Note: We don't have to put the block container onto the work queue,
// because it's an ancestor of the Leave instruction, and hence
// we are currently somewhere within the VisitBlockContainer() call.
state.MarkUnreachable();
}
protected internal override void VisitReturn(Return inst)
{
if (inst.ReturnValue != null)
inst.ReturnValue.AcceptVisitor(this);
state.MarkUnreachable();
}
protected internal override void VisitThrow(Throw inst)
{
inst.Argument.AcceptVisitor(this);
MayThrow();
state.MarkUnreachable();
}
protected internal override void VisitRethrow(Rethrow inst)
{
MayThrow();
state.MarkUnreachable();
}
/// <summary>
/// Visits the TryBlock.
///
/// Returns a new State object representing the exceptional control flow transfer out of the try block.
/// </summary>
protected State HandleTryBlock(TryInstruction inst)
{
State oldStateOnException = stateOnException;
State newStateOnException = unreachableState.Clone();
stateOnException = newStateOnException;
inst.TryBlock.AcceptVisitor(this);
stateOnException = oldStateOnException;
return newStateOnException;
}
protected internal override void VisitTryCatch(TryCatch inst)
{
State caughtState = HandleTryBlock(inst);
State endpointState = state.Clone();
// The exception might get propagated if no handler matches the type:
stateOnException.JoinWith(caughtState);
foreach (var handler in inst.Handlers) {
state.ReplaceWith(caughtState);
BeginTryCatchHandler(handler);
handler.Filter.AcceptVisitor(this);
// if the filter return false, any mutations done by the filter
// will be visible by the remaining handlers
// (but it's also possible that the filter didn't get executed at all
// because the exception type doesn't match)
caughtState.JoinWith(state);
handler.Body.AcceptVisitor(this);
endpointState.JoinWith(state);
}
state = endpointState;
}
protected virtual void BeginTryCatchHandler(TryCatchHandler inst)
{
}
/// <summary>
/// TryCatchHandler is handled directly in VisitTryCatch
/// </summary>
protected internal override sealed void VisitTryCatchHandler(TryCatchHandler inst)
{
throw new NotImplementedException();
}
protected internal override void VisitTryFinally(TryFinally inst)
{
// I don't think there's a good way to track dataflow across finally blocks
// without duplicating the whole finally block.
// We'll just approximate 'try { .. } finally { .. }' as 'try { .. } catch {} .. if (?) rethrow; }'
State caughtState = HandleTryBlock(inst);
State onSuccess = state.Clone();
state.JoinWith(caughtState);
inst.FinallyBlock.AcceptVisitor(this);
MayThrow();
// Our approximation allows the impossible code path where the try block wasn't fully executed
// and the finally block did not rethrow the exception.
// We can't fix up the data flow state in general, but specific analyses may be able to do so.
ExitTryFinally(inst, onSuccess);
}
/// <summary>
/// Called when exiting a try-finally construct.
/// </summary>
/// <param name="inst">The try-finally instruction.</param>
/// <param name="onSuccess">The state at the endpoint of the try block.</param>
/// <c>state</c>: The current state when ExitTryFinally() is called is the state at the
/// endpoint of the finally block.
/// After ExitTryFinally() exits, it should be the state at the endpoint
/// of the whole try-finally construct.
/// <remarks>
/// The purpose of this method is to allow the derived class to fix up the state at the end
/// of the try-finally construct: the end of the finally block can be reached when
/// the the try block has throws an exception, but the endpoint of the whole try-finally
/// is only reachable both the try block and the finally block execute normally.
/// </remarks>
protected virtual void ExitTryFinally(TryFinally inst, State onSuccess)
{
if (onSuccess.IsUnreachable) {
state.MarkUnreachable();
}
}
protected internal override void VisitTryFault(TryFault inst)
{
// try-fault executes fault block if an exception occurs in try,
// and always rethrows the exception at the end.
State caughtState = HandleTryBlock(inst);
State noException = state;
state = caughtState;
inst.FaultBlock.AcceptVisitor(this);
MayThrow(); // rethrow the exception after the fault block
// try-fault exits normally only if no exception occurred
state = noException;
}
protected internal override void VisitIfInstruction(IfInstruction inst)
{
inst.Condition.AcceptVisitor(this);
State branchState = state.Clone();
inst.TrueInst.AcceptVisitor(this);
State afterTrueState = state;
state = branchState;
inst.FalseInst.AcceptVisitor(this);
state.JoinWith(afterTrueState);
}
protected internal override void VisitSwitchInstruction(SwitchInstruction inst)
{
inst.Value.AcceptVisitor(this);
State beforeSections = state.Clone();
State afterSections = unreachableState.Clone();
foreach (var section in inst.Sections) {
state.ReplaceWith(beforeSections);
section.AcceptVisitor(this);
afterSections.JoinWith(state);
}
state = afterSections;
}
}
}

171
ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs

@ -0,0 +1,171 @@ @@ -0,0 +1,171 @@
// 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.Diagnostics;
using ICSharpCode.Decompiler.IL;
namespace ICSharpCode.Decompiler.FlowAnalysis
{
/// <summary>
/// DataFlowVisitor that performs definite assignment analysis.
/// </summary>
class DefiniteAssignmentVisitor : DataFlowVisitor<DefiniteAssignmentVisitor.State>
{
const int ReachableBit = 0;
/// <summary>
/// State for definite assignment analysis.
/// </summary>
[DebuggerDisplay("{bits}")]
public struct State : IDataFlowState<State>
{
/// <summary>
/// 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 does not write to function.Variables[i].
///
/// Initial state: all bits set
/// "Unreachable" state: all bits clear
/// </summary>
readonly BitSet bits;
/// <summary>
/// Creates the initial state.
/// </summary>
public State(int variableCount)
{
this.bits = new BitSet(1 + variableCount);
this.bits.SetAll();
}
private State(BitSet bits)
{
this.bits = bits;
}
public bool LessThanOrEqual(State otherState)
{
return bits.IsSubsetOf(otherState.bits);
}
public State Clone()
{
return new State(bits.Clone());
}
public void ReplaceWith(State newContent)
{
bits.ReplaceWith(newContent.bits);
}
public void JoinWith(State incomingState)
{
bits.UnionWith(incomingState.bits);
}
public void IntersectWith(State incomingState)
{
bits.IntersectWith(incomingState.bits);
}
public void MarkUnreachable()
{
bits.ClearAll();
}
public bool IsUnreachable {
get { return !bits[ReachableBit]; }
}
public void MarkVariableInitialized(int variableIndex)
{
bits.Clear(1 + variableIndex);
}
public bool IsPotentiallyUninitialized(int variableIndex)
{
return bits[1 + variableIndex];
}
}
readonly ILVariableScope scope;
readonly BitSet variablesWithUninitializedUsage;
public DefiniteAssignmentVisitor(ILVariableScope scope) : base(new State(scope.Variables.Count))
{
this.scope = scope;
this.variablesWithUninitializedUsage = new BitSet(scope.Variables.Count);
}
public bool IsPotentiallyUsedUninitialized(ILVariable v)
{
Debug.Assert(v.Scope == scope);
return variablesWithUninitializedUsage[v.IndexInScope];
}
void HandleStore(ILVariable v)
{
if (v.Scope == scope && !state.IsUnreachable) {
// Clear the set of stores for this variable:
state.MarkVariableInitialized(v.IndexInScope);
}
}
void EnsureInitialized(ILVariable v)
{
if (v.Scope == scope && state.IsPotentiallyUninitialized(v.IndexInScope)) {
variablesWithUninitializedUsage.Set(v.IndexInScope);
}
}
protected internal override void VisitStLoc(StLoc inst)
{
base.VisitStLoc(inst);
HandleStore(inst.Variable);
}
protected override void BeginTryCatchHandler(TryCatchHandler inst)
{
HandleStore(inst.Variable);
base.BeginTryCatchHandler(inst);
}
protected internal override void VisitLdLoc(LdLoc inst)
{
base.VisitLdLoc(inst);
EnsureInitialized(inst.Variable);
}
protected internal override void VisitLdLoca(LdLoca inst)
{
base.VisitLdLoca(inst);
EnsureInitialized(inst.Variable);
}
protected override void ExitTryFinally(TryFinally inst, State onSuccess)
{
// We can simply intersect the onSuccess bits with the state after the finally block.
// This works because the try-finally endpoint is reached (=control flow continues after the try-finally construct)
// without writing to variable i,
// only if the endpoint of the try was reached with writing to variable i,
// and the endpoint of the finally block was also reached without writing to variable i.
state.IntersectWith(onSuccess);
}
}
}

417
ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitions.cs

@ -43,52 +43,71 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -43,52 +43,71 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
/// </remarks>
public class ReachingDefinitions
{
#region Documentation + member fields
#region State representation
/// <summary>
/// A special Nop instruction that gets used as a fake store if the variable
/// is possibly uninitialized.
/// The state during the reaching definitions analysis.
/// </summary>
public static readonly ILInstruction UninitializedVariable = new Nop();
/// <remarks>
/// A state can either be reachable, or unreachable:
/// 1) unreachable
/// Note that during the analysis, "unreachable" just means we have not yet found a path
/// from the entry point to the node. States transition from unreachable to reachable as
/// the analysis processes more control flow paths.
/// 2) reachable
/// In this case, the state contains, for each variable, the set of stores that might have
/// written to the variable before the control flow reached the state's source code position.
/// This set does not include stores that were definitely overwritten by other stores to the
/// same variable.
/// During the analysis, the set of stores gets extended as the analysis processes more code paths.
///
/// The reachable state could be represented as a `Dictionary{ILVariable, ISet{ILInstruction}}`.
/// To consume less memory, we instead assign an integer index to all stores in the analyzed function ("store index"),
/// and store the state as a `BitSet` instead.
/// Each bit in the set corresponds to one store instruction, and is `true` iff the store is a reaching definition
/// for the variable it is storing to.
/// The `allStores` array has the same length as the bit sets and holds the corresponding `ILInstruction` objects (store instructions).
/// All stores for a single variable occupy a contiguous segment of the `allStores` array (and thus also of the `state`),
/// which allows us to efficient clear out all stores that get overwritten by a new store.
/// </remarks>
[DebuggerDisplay("{bits}")]
struct State : IDataFlowState<State>
{
readonly internal BitSet bits;
// The reaching definition analysis tracks a 'state'.
// This 'state' represents the reaching definitions at a given source code position.
// There are many states (one per source code position, i.e. ILInstruction),
// but we don't store all of them.
// We only keep track of:
// a) the current state in the RDVisitor
// This state corresponds to the instruction currently being visited,
// and gets mutated as we traverse the ILAst.
// b) the input state for each control flow node
// This also gets mutated as the analysis learns about new control flow edges.
//
// A state can either be reachable, or unreachable:
// 1) unreachable
// Note that during the analysis, "unreachable" just means we have not yet found a path
// from the entry point to the node. States transition from unreachable to reachable as
// the analysis processes more control flow paths.
// 2) reachable
// In this case, the state contains, for each variable, the set of stores that might have
// written to the variable before the control flow reached the state's source code position.
// This set does not include stores that were definitely overwritten by other stores to the
// same variable.
// During the analysis, the set of stores gets extended as the analysis processes more code paths.
// For loops, we need to analyze the loop body before we can know the state for the loop backedge,
// but we need to know the input state for the loop body (to which the backedge state contributes)
// before we can analyze the loop body.
// Solution: we repeat the analysis of the loop body multiple times, until the state no longer changes.
// Because all state changes are irreversible (we only add to the set of stores, we never remove from it),
// this algorithm will eventually terminate.
// To make it terminate reasonably quickly, we need to process the control flow nodes in the correct order:
// reverse post-order. This class assumes the blocks in each block container are already sorted appropriately.
// The caller can use BlockContainer.SortBlocks() for this.
public State(BitSet bits)
{
this.bits = bits;
}
// The state as described above could be represented as a `Dictionary<ILVariable, ImmutableHashSet<ILInstruction>>`.
// To consume less memory, we instead assign an integer index to all stores in the analyzed function ("store index"),
// and store the state as a `BitSet` instead.
// Each bit in the set corresponds to one store instruction, and is `true` iff the store is a reaching definition
// for the variable it is storing to.
// The `allStores` array has the same length as the bit sets and holds the corresponding `ILInstruction` objects (store instructions).
// All stores for a single variable occupy a contiguous segment of the `allStores` array (and thus also of the `state`).
public bool LessThanOrEqual(State otherState)
{
return bits.IsSubsetOf(otherState.bits);
}
public State Clone()
{
return new State(bits.Clone());
}
public void ReplaceWith(State newContent)
{
bits.ReplaceWith(newContent.bits);
}
public void JoinWith(State incomingState)
{
bits.UnionWith(incomingState.bits);
}
public void MarkUnreachable()
{
bits.ClearAll();
}
public bool IsUnreachable {
get { return !bits[ReachableBit]; }
}
}
/// <summary>
/// To distinguish unreachable from reachable states, we use the first bit in the bitset to store the 'reachable bit'.
@ -100,6 +119,14 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -100,6 +119,14 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
/// Because bit number 0 is the ReachableBit, we start counting store indices at 1.
/// </summary>
const int FirstStoreIndex = 1;
#endregion
#region Documentation + member fields
/// <summary>
/// A special Nop instruction that gets used as a fake store if the variable
/// is possibly uninitialized.
/// </summary>
public static readonly ILInstruction UninitializedVariable = new Nop();
/// <summary>
/// The function being analyzed.
@ -136,19 +163,6 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -136,19 +163,6 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
/// <c>activeVariable[v.IndexInScope]</c> is true iff RD analysis is enabled for the variable.
/// </summary>
readonly BitSet activeVariables;
/// <summary>
/// Holds the state for incoming branches.
/// </summary>
/// <remarks>
/// Only used for blocks in block containers; not for inline blocks.
/// </remarks>
readonly Dictionary<Block, BitSet> stateOnBranch = new Dictionary<Block, BitSet>();
/// <summary>
/// Holds the state at the block container end-point. (=state for incoming 'leave' instructions)
/// </summary>
readonly Dictionary<BlockContainer, BitSet> stateOnLeave = new Dictionary<BlockContainer, BitSet>();
#endregion
#region Constructor
@ -193,11 +207,11 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -193,11 +207,11 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
var stores = storesByVar[vi];
if (stores != null) {
int expectedStoreCount = scope.Variables[vi].StoreCount;
if (scope.Variables[vi].Kind != VariableKind.Parameter && scope.Variables[vi].Kind != VariableKind.This) {
if (!scope.Variables[vi].HasInitialValue) {
// Extra store for UninitializedVariable
expectedStoreCount += 1;
// Note that for VariableKind.Parameter/This, this extra store
// is already accounted for in ILVariable.StoreCount.
// 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);
@ -211,8 +225,6 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -211,8 +225,6 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
firstStoreIndexForVariable[scope.Variables.Count] = si;
Debug.Assert(si == allStores.Length);
this.workList = CreateWorkLists(scope);
InitStateDictionaries();
RDVisitor visitor = new RDVisitor(this);
scope.Children.Single().AcceptVisitor(visitor);
}
@ -240,11 +252,11 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -240,11 +252,11 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
}
#endregion
#region State Management
#region CreateInitialState
/// <summary>
/// Create the initial state (reachable + all variables uninitialized).
/// </summary>
BitSet CreateInitialState()
State CreateInitialState()
{
BitSet initialState = new BitSet(allStores.Length);
initialState.Set(ReachableBit);
@ -254,298 +266,41 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -254,298 +266,41 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
initialState.Set(firstStoreIndexForVariable[vi]);
}
}
return initialState;
}
BitSet CreateUnreachableState()
{
return new BitSet(allStores.Length);
}
void InitStateDictionaries()
{
foreach (var container in scope.Descendants.OfType<BlockContainer>()) {
foreach (var block in container.Blocks) {
stateOnBranch.Add(block, CreateUnreachableState());
}
stateOnLeave.Add(container, CreateUnreachableState());
}
}
/// <summary>
/// Merge <c>incomingState</c> into <c>state</c>.
/// </summary>
/// <returns>
/// Returns true if <c>state</c> was modified.
/// </returns>
static bool MergeState(BitSet state, BitSet incomingState)
{
if (!incomingState[ReachableBit]) {
// MergeState() is a no-op if the incoming state is unreachable
return false;
}
if (state[ReachableBit]) {
// both reachable: state |= incomingState;
if (state.IsSupersetOf(incomingState)) {
// UnionWith() wouldn't actually change the state,
// so we have to return false
return false;
}
state.UnionWith(incomingState);
} else {
state.ReplaceWith(incomingState);
}
return true;
}
#endregion
#region Worklist
/// <summary>
/// For each block container, stores the set of blocks (via Block.ChildIndex) that had their incoming state
/// changed and were not processed yet.
/// </summary>
readonly Dictionary<BlockContainer, SortedSet<int>> workList;
static Dictionary<BlockContainer, SortedSet<int>> CreateWorkLists(ILVariableScope scope)
{
var worklists = new Dictionary<BlockContainer, SortedSet<int>>();
foreach (var container in scope.Descendants.OfType<BlockContainer>()) {
worklists.Add(container, new SortedSet<int>());
}
return worklists;
}
/// <summary>
/// The work list keeps track of which blocks had their incoming state updated,
/// but did not run yet.
/// </summary>
void AddToWorkList(Block block)
{
BlockContainer container = (BlockContainer)block.Parent;
workList[container].Add(block.ChildIndex);
return new State(initialState);
}
#endregion
/// <summary>
/// Visitor that traverses the ILInstruction tree.
/// </summary>
class RDVisitor : ILVisitor
class RDVisitor : DataFlowVisitor<State>
{
readonly ReachingDefinitions rd;
internal RDVisitor(ReachingDefinitions rd)
internal RDVisitor(ReachingDefinitions rd) : base(rd.CreateInitialState())
{
this.rd = rd;
this.state = rd.CreateInitialState();
this.stateOnException = rd.CreateUnreachableState();
}
/// <summary>
/// Combines state of all possible exceptional control flow paths in the current try block.
/// </summary>
BitSet stateOnException;
/// <summary>
/// Current state.
/// Gets mutated as the visitor traverses the ILAst.
/// </summary>
BitSet state;
protected override void Default(ILInstruction inst)
{
// This method assumes normal control flow and no branches.
if ((inst.DirectFlags & (InstructionFlags.ControlFlow | InstructionFlags.MayBranch | InstructionFlags.EndPointUnreachable)) != 0) {
throw new NotImplementedException("RDVisitor is missing implementation for " + inst.GetType().Name);
}
// Since this instruction has normal control flow, we can evaluate our children left-to-right.
foreach (var child in inst.Children) {
child.AcceptVisitor(this);
Debug.Assert(!(state[ReachableBit] && child.HasFlag(InstructionFlags.EndPointUnreachable)));
}
// If this instruction can throw an exception, handle the exceptional control flow edge.
if ((inst.DirectFlags & InstructionFlags.MayThrow) != 0) {
MayThrow();
}
}
/// <summary>
/// Handle control flow when `throwInst` throws an exception.
/// </summary>
void MayThrow()
void HandleStore(ILInstruction inst, ILVariable v)
{
MergeState(stateOnException, state);
}
void MarkUnreachable()
{
state.Clear(ReachableBit);
}
protected internal override void VisitStLoc(StLoc inst)
{
Default(inst);
ILVariable v = inst.Variable;
if (v.Scope == rd.scope && rd.activeVariables[v.IndexInScope]) {
if (v.Scope == rd.scope && rd.activeVariables[v.IndexInScope] && !state.IsUnreachable) {
// Clear the set of stores for this variable:
state.Clear(rd.firstStoreIndexForVariable[v.IndexInScope],
rd.firstStoreIndexForVariable[v.IndexInScope + 1]);
state.bits.Clear(rd.firstStoreIndexForVariable[v.IndexInScope], rd.firstStoreIndexForVariable[v.IndexInScope + 1]);
// And replace it with this store:
state.Set(rd.storeIndexMap[inst]);
}
}
protected internal override void VisitBlockContainer(BlockContainer container)
{
SortedSet<int> worklist = rd.workList[container];
if (MergeState(rd.stateOnBranch[container.EntryPoint], state)) {
worklist.Add(0); // add container entry point to work list
}
// Because we use a SortedSet for the work list,
// we always process the blocks in the same order as they are in the container
// (usually reverse post-order).
while (worklist.Count > 0) {
int blockIndex = worklist.Min;
worklist.Remove(blockIndex);
Block block = container.Blocks[blockIndex];
state.ReplaceWith(rd.stateOnBranch[block]);
block.AcceptVisitor(this);
}
state.ReplaceWith(rd.stateOnLeave[container]);
}
protected internal override void VisitBranch(Branch inst)
{
if (MergeState(rd.stateOnBranch[inst.TargetBlock], state)) {
rd.AddToWorkList(inst.TargetBlock);
state.bits.Set(rd.storeIndexMap[inst]);
}
MarkUnreachable();
}
protected internal override void VisitLeave(Leave inst)
{
Debug.Assert(inst.IsDescendantOf(inst.TargetContainer));
MergeState(rd.stateOnLeave[inst.TargetContainer], state);
// Note: We don't have to put the block container onto the work queue,
// because it's an ancestor of the Leave instruction, and hence
// we are currently somewhere within the VisitBlockContainer() call.
MarkUnreachable();
}
protected internal override void VisitReturn(Return inst)
{
if (inst.ReturnValue != null)
inst.ReturnValue.AcceptVisitor(this);
MarkUnreachable();
}
protected internal override void VisitThrow(Throw inst)
{
inst.Argument.AcceptVisitor(this);
MayThrow();
MarkUnreachable();
}
protected internal override void VisitRethrow(Rethrow inst)
{
MayThrow();
MarkUnreachable();
}
/// <summary>
/// Visits the TryBlock.
///
/// Returns a new BitSet representing the state of exceptional control flow transfer
/// out of the try block.
/// </summary>
BitSet HandleTryBlock(TryInstruction inst)
{
BitSet oldStateOnException = stateOnException;
BitSet newStateOnException = rd.CreateUnreachableState();
stateOnException = newStateOnException;
inst.TryBlock.AcceptVisitor(this);
stateOnException = oldStateOnException;
return newStateOnException;
}
protected internal override void VisitTryCatch(TryCatch inst)
{
BitSet caughtState = HandleTryBlock(inst);
BitSet endpointState = state.Clone();
// The exception might get propagated if no handler matches the type:
MergeState(stateOnException, caughtState);
foreach (var handler in inst.Handlers) {
state.ReplaceWith(caughtState);
handler.Filter.AcceptVisitor(this);
// if the filter return false, any mutations done by the filter
// will be visible by the remaining handlers
// (but it's also possible that the filter didn't get executed at all
// because the exception type doesn't match)
MergeState(caughtState, state);
handler.Body.AcceptVisitor(this);
MergeState(endpointState, state);
}
state = endpointState;
}
protected internal override void VisitTryFinally(TryFinally inst)
{
// I don't think there's a good way to track dataflow across finally blocks
// without duplicating the whole finally block.
// We'll just approximate 'try { .. } finally { .. }' as 'try { .. } catch {} .. if (?) rethrow; }'
BitSet caughtState = HandleTryBlock(inst);
MergeState(state, caughtState);
inst.FinallyBlock.AcceptVisitor(this);
MayThrow();
// Our approximation allows the impossible code path where the try block wasn't fully executed
// and the finally block did not rethrow the exception.
// This can cause us to not be marked unreachable in cases where the simple InstructionFlags
// know the path is unreachable -- so use the flag to fix the reachable bit.
if (inst.HasFlag(InstructionFlags.EndPointUnreachable)) {
MarkUnreachable();
}
}
protected internal override void VisitTryFault(TryFault inst)
{
// try-fault executes fault block if an exception occurs in try,
// and always rethrows the exception at the end.
BitSet caughtState = HandleTryBlock(inst);
BitSet noException = state;
state = caughtState;
inst.FaultBlock.AcceptVisitor(this);
MayThrow(); // rethrow the exception after the fault block
// try-fault exits normally only if no exception occurred
state = noException;
}
protected internal override void VisitIfInstruction(IfInstruction inst)
protected internal override void VisitStLoc(StLoc inst)
{
inst.Condition.AcceptVisitor(this);
BitSet branchState = state.Clone();
inst.TrueInst.AcceptVisitor(this);
BitSet afterTrueState = state;
state = branchState;
inst.FalseInst.AcceptVisitor(this);
MergeState(state, afterTrueState);
base.VisitStLoc(inst);
HandleStore(inst, inst.Variable);
}
protected internal override void VisitSwitchInstruction(SwitchInstruction inst)
protected override void BeginTryCatchHandler(TryCatchHandler inst)
{
inst.Value.AcceptVisitor(this);
BitSet beforeSections = state.Clone();
BitSet afterSections = rd.CreateUnreachableState();
foreach (var section in inst.Sections) {
state.ReplaceWith(beforeSections);
section.AcceptVisitor(this);
MergeState(afterSections, state);
}
state = afterSections;
HandleStore(inst, inst.Variable);
}
}
}

4
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -83,6 +83,8 @@ @@ -83,6 +83,8 @@
<Compile Include="CSharp\Transforms\IntroduceUnsafeModifier.cs" />
<Compile Include="CSharp\Transforms\ReplaceMethodCallsWithOperators.cs" />
<Compile Include="FlowAnalysis\ControlFlowNode.cs" />
<Compile Include="FlowAnalysis\DataFlowVisitor.cs" />
<Compile Include="FlowAnalysis\DefiniteAssignmentVisitor.cs" />
<Compile Include="FlowAnalysis\Dominance.cs" />
<Compile Include="FlowAnalysis\ReachingDefinitions.cs" />
<Compile Include="IL\ControlFlow\ConditionDetection.cs" />
@ -123,7 +125,7 @@ @@ -123,7 +125,7 @@
<Compile Include="IL\Transforms\ExpressionTransforms.cs" />
<Compile Include="IL\Transforms\InlineCompilerGeneratedVariables.cs" />
<Compile Include="IL\Transforms\LoopingTransform.cs" />
<Compile Include="IL\Transforms\SplitVariables.cs" />
<Compile Include="IL\Transforms\RemoveDeadVariableInit.cs" />
<Compile Include="IL\Transforms\TransformArrayInitializers.cs" />
<Compile Include="IL\Transforms\TransformingVisitor.cs" />
<Compile Include="CecilExtensions.cs" />

23
ICSharpCode.Decompiler/IL/ILReader.cs

@ -82,6 +82,11 @@ namespace ICSharpCode.Decompiler.IL @@ -82,6 +82,11 @@ namespace ICSharpCode.Decompiler.IL
this.unionFind = new UnionFind<ILVariable>();
InitParameterVariables();
this.localVariables = body.Variables.SelectArray(CreateILVariable);
if (body.InitLocals) {
foreach (var v in localVariables) {
v.HasInitialValue = true;
}
}
this.instructionBuilder = new List<ILInstruction>();
this.isBranchTarget = new BitArray(body.CodeSize);
this.stackByOffset = new Dictionary<int, ImmutableStack<ILVariable>>();
@ -128,7 +133,8 @@ namespace ICSharpCode.Decompiler.IL @@ -128,7 +133,8 @@ namespace ICSharpCode.Decompiler.IL
ILVariable CreateILVariable(Cil.VariableDefinition v)
{
var ilVar = new ILVariable(VariableKind.Local, typeSystem.Resolve(v.VariableType), v.Index);
VariableKind kind = v.IsPinned ? VariableKind.PinnedLocal : VariableKind.Local;
ILVariable ilVar = new ILVariable(kind, typeSystem.Resolve(v.VariableType), v.Index);
if (string.IsNullOrEmpty(v.Name))
ilVar.Name = "V_" + v.Index;
else
@ -138,10 +144,8 @@ namespace ICSharpCode.Decompiler.IL @@ -138,10 +144,8 @@ namespace ICSharpCode.Decompiler.IL
ILVariable CreateILVariable(ParameterDefinition p)
{
VariableKind variableKind;
IType parameterType;
if (p.Index == -1) {
variableKind = VariableKind.This;
// Manually construct ctor parameter type due to Cecil bug:
// https://github.com/jbevain/cecil/issues/275
ITypeDefinition def = typeSystem.Resolve(body.Method.DeclaringType).GetDefinition();
@ -154,7 +158,6 @@ namespace ICSharpCode.Decompiler.IL @@ -154,7 +158,6 @@ namespace ICSharpCode.Decompiler.IL
parameterType = typeSystem.Resolve(p.ParameterType);
}
} else {
variableKind = VariableKind.Parameter;
parameterType = typeSystem.Resolve(p.ParameterType);
}
Debug.Assert(!parameterType.IsUnbound());
@ -163,9 +166,9 @@ namespace ICSharpCode.Decompiler.IL @@ -163,9 +166,9 @@ namespace ICSharpCode.Decompiler.IL
Debug.Assert(p.Index < 0); // cecil bug occurs only for "this"
parameterType = new ParameterizedType(parameterType.GetDefinition(), parameterType.TypeArguments);
}
var ilVar = new ILVariable(variableKind, parameterType, p.Index);
ilVar.StoreCount = 1; // count the initial store when the method is called with an argument
if (variableKind == VariableKind.This)
var ilVar = new ILVariable(VariableKind.Parameter, parameterType, p.Index);
Debug.Assert(ilVar.StoreCount == 1); // count the initial store when the method is called with an argument
if (p.Index < 0)
ilVar.Name = "this";
else if (string.IsNullOrEmpty(p.Name))
ilVar.Name = "P_" + p.Index;
@ -293,13 +296,17 @@ namespace ICSharpCode.Decompiler.IL @@ -293,13 +296,17 @@ namespace ICSharpCode.Decompiler.IL
{
Init(body);
ReadInstructions(cancellationToken);
var container = new BlockBuilder(body, typeSystem, variableByExceptionHandler).CreateBlocks(instructionBuilder, isBranchTarget);
var blockBuilder = new BlockBuilder(body, typeSystem, variableByExceptionHandler);
var container = blockBuilder.CreateBlocks(instructionBuilder, isBranchTarget);
var function = new ILFunction(body.Method, container);
function.Variables.AddRange(parameterVariables);
function.Variables.AddRange(localVariables);
function.Variables.AddRange(stackVariables);
function.Variables.AddRange(variableByExceptionHandler.Values);
function.AddRef(); // mark the root node
foreach (var c in function.Descendants.OfType<BlockContainer>()) {
c.SortBlocks();
}
return function;
}

85
ICSharpCode.Decompiler/IL/ILVariable.cs

@ -37,13 +37,12 @@ namespace ICSharpCode.Decompiler.IL @@ -37,13 +37,12 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
Parameter,
/// <summary>
/// The 'this' parameter
/// </summary>
This,
/// <summary>
/// Variable created for exception handler
/// </summary>
Exception,
/// <summary>
/// Variable created from stack slot.
/// </summary>
StackSlot
}
@ -83,15 +82,22 @@ namespace ICSharpCode.Decompiler.IL @@ -83,15 +82,22 @@ namespace ICSharpCode.Decompiler.IL
/// <remarks>
/// This variable is automatically updated when adding/removing ldloc instructions from the ILAst.
/// </remarks>
public int LoadCount;
public int LoadCount { get; internal set; }
/// <summary>
/// Number of stloc instructions referencing this variable.
/// Number of store instructions referencing this variable.
///
/// Stores are:
/// <list type="item">
/// <item>stloc</item>
/// <item>try.catch.handler (assigning the exception variable)</item>
/// <item>initial values (<see cref="HasInitialValue"/>)</item>
/// </list>
/// </summary>
/// <remarks>
/// This variable is automatically updated when adding/removing stloc instructions from the ILAst.
/// This variable is automatically updated when adding/removing stores instructions from the ILAst.
/// </remarks>
public int StoreCount;
public int StoreCount { get; internal set; }
/// <summary>
/// Number of ldloca instructions referencing this variable.
@ -99,7 +105,34 @@ namespace ICSharpCode.Decompiler.IL @@ -99,7 +105,34 @@ namespace ICSharpCode.Decompiler.IL
/// <remarks>
/// This variable is automatically updated when adding/removing ldloca instructions from the ILAst.
/// </remarks>
public int AddressCount;
public int AddressCount { get; internal set; }
bool hasInitialValue;
/// <summary>
/// Gets/Sets whether the variable has an initial value.
/// This is always <c>true</c> for parameters (incl. <c>this</c>).
///
/// Normal variables have an initial value if the function uses ".locals init"
/// and that initialization is not a dead store.
/// </summary>
/// <remarks>
/// An initial value is counted as a store (adds 1 to StoreCount)
/// </remarks>
public bool HasInitialValue {
get { return hasInitialValue; }
set {
if (hasInitialValue) {
if (Kind == VariableKind.Parameter)
throw new InvalidOperationException("Cannot remove HasInitialValue from parameters");
StoreCount--;
}
hasInitialValue = value;
if (value) {
StoreCount++;
}
}
}
public bool IsSingleDefinition {
get {
@ -115,6 +148,8 @@ namespace ICSharpCode.Decompiler.IL @@ -115,6 +148,8 @@ namespace ICSharpCode.Decompiler.IL
this.Type = type;
this.StackType = type.GetStackType();
this.Index = index;
if (kind == VariableKind.Parameter)
this.HasInitialValue = true;
}
public ILVariable(VariableKind kind, IType type, StackType stackType, int index)
@ -123,6 +158,8 @@ namespace ICSharpCode.Decompiler.IL @@ -123,6 +158,8 @@ namespace ICSharpCode.Decompiler.IL
this.Type = type;
this.StackType = stackType;
this.Index = index;
if (kind == VariableKind.Parameter)
this.HasInitialValue = true;
}
public override string ToString()
@ -132,12 +169,36 @@ namespace ICSharpCode.Decompiler.IL @@ -132,12 +169,36 @@ namespace ICSharpCode.Decompiler.IL
internal void WriteDefinitionTo(ITextOutput output)
{
switch (Kind) {
case VariableKind.Local:
output.Write("local ");
break;
case VariableKind.PinnedLocal:
output.Write("pinned local ");
break;
case VariableKind.Parameter:
output.Write("param ");
break;
case VariableKind.Exception:
output.Write("exception ");
break;
case VariableKind.StackSlot:
output.Write("stack ");
break;
default:
throw new ArgumentOutOfRangeException();
}
output.WriteDefinition(this.Name, this, isLocal: true);
output.Write(" : ");
if (Kind == VariableKind.PinnedLocal)
output.Write("pinned ");
Type.WriteTo(output);
output.Write("({0} ldloc, {1} ldloca, {2} stloc)", LoadCount, AddressCount, StoreCount);
output.Write('(');
if (Kind == VariableKind.Parameter || Kind == VariableKind.Local || Kind == VariableKind.PinnedLocal) {
output.Write("Index={0}, ", Index);
}
output.Write("LoadCount={0}, AddressCount={1}, StoreCount={2})", LoadCount, AddressCount, StoreCount);
if (hasInitialValue && Kind != VariableKind.Parameter) {
output.Write(" init");
}
}
internal void WriteTo(ITextOutput output)

4
ICSharpCode.Decompiler/IL/InstructionFlags.cs

@ -36,7 +36,7 @@ namespace ICSharpCode.Decompiler.IL @@ -36,7 +36,7 @@ namespace ICSharpCode.Decompiler.IL
/// The instruction may write to local variables.
/// </summary>
/// <remarks>
/// This flag is not set for indirect write to local variables through pointers.
/// This flag is not set for indirect writes to local variables through pointers.
/// Ensure you also check the SideEffect flag.
/// </remarks>
MayWriteLocals = 0x20,
@ -45,7 +45,7 @@ namespace ICSharpCode.Decompiler.IL @@ -45,7 +45,7 @@ namespace ICSharpCode.Decompiler.IL
/// performing system calls, writing to local variables through pointers, etc.
/// </summary>
/// <remarks>
/// Throwing an exception or directly writing to local variables or the evaluation stack
/// Throwing an exception or directly writing to local variables
/// is not considered a side effect, and is modeled by separate flags.
/// </remarks>
SideEffect = 0x40,

2
ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs

@ -29,8 +29,6 @@ namespace ICSharpCode.Decompiler.IL @@ -29,8 +29,6 @@ namespace ICSharpCode.Decompiler.IL
{
public abstract partial class CallInstruction : ILInstruction
{
public static readonly SlotInfo ArgumentSlot = new SlotInfo("Argument", canInlineInto: true, isCollection: true);
public static CallInstruction Create(OpCode opCode, IMethod method)
{
switch (opCode) {

2
ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs

@ -42,7 +42,7 @@ namespace ICSharpCode.Decompiler.IL @@ -42,7 +42,7 @@ namespace ICSharpCode.Decompiler.IL
public bool MatchLdThis()
{
var inst = this as LdLoc;
return inst != null && inst.Variable.Kind == VariableKind.This;
return inst != null && inst.Variable.Kind == VariableKind.Parameter && inst.Variable.Index < 0;
}
public bool MatchStLoc(out ILVariable variable)

6
ICSharpCode.Decompiler/IL/SemanticHelper.cs

@ -22,7 +22,7 @@ namespace ICSharpCode.Decompiler.IL @@ -22,7 +22,7 @@ namespace ICSharpCode.Decompiler.IL
{
static class SemanticHelper
{
// TODO: consider moving IfInstruction.CombineFlags and Block.Phase1Boundary here
// TODO: consider moving IfInstruction.CombineFlags here
/// <summary>
/// Gets whether instruction is pure:
@ -32,7 +32,9 @@ namespace ICSharpCode.Decompiler.IL @@ -32,7 +32,9 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
internal static bool IsPure(InstructionFlags inst)
{
const InstructionFlags pureFlags = InstructionFlags.MayReadLocals;
// ControlFlow is fine, internal control flow is pure as long as it's not an infinite loop,
// and infinite loops are impossible without MayBranch.
const InstructionFlags pureFlags = InstructionFlags.MayReadLocals | InstructionFlags.ControlFlow;
return (inst & ~pureFlags) == 0;
}

3
ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs

@ -38,7 +38,7 @@ namespace ICSharpCode.Decompiler.IL @@ -38,7 +38,7 @@ namespace ICSharpCode.Decompiler.IL
ILVariable v;
ILInstruction copiedExpr;
if (block.Instructions[i].MatchStLoc(out v, out copiedExpr)) {
if (v.IsSingleDefinition && v.Kind != VariableKind.Parameter && CanPerformCopyPropagation(v, copiedExpr)) {
if (v.IsSingleDefinition && CanPerformCopyPropagation(v, copiedExpr)) {
// un-inline the arguments of the ldArg instruction
ILVariable[] uninlinedArgs = new ILVariable[copiedExpr.Children.Count];
for (int j = 0; j < uninlinedArgs.Length; j++) {
@ -82,7 +82,6 @@ namespace ICSharpCode.Decompiler.IL @@ -82,7 +82,6 @@ namespace ICSharpCode.Decompiler.IL
case OpCode.LdLoc:
var v = ((LdLoc)value).Variable;
switch (v.Kind) {
case VariableKind.This:
case VariableKind.Parameter:
// Parameters can be copied only if they aren't assigned to (directly or indirectly via ldarga)
// note: the initialization by the caller is the first store -> StoreCount must be 1

16
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -15,7 +15,9 @@ @@ -15,7 +15,9 @@
// 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.Linq;
using ICSharpCode.NRefactory.TypeSystem;
@ -64,11 +66,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -64,11 +66,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
protected internal override void VisitCall(Call inst)
{
base.VisitCall(inst);
if (!inst.Method.IsConstructor || inst.Method.DeclaringType.Kind != TypeKind.Struct)
return;
if (inst.Arguments.Count != inst.Method.Parameters.Count + 1)
return;
if (inst.Method.IsConstructor && !inst.Method.IsStatic && inst.Method.DeclaringType.Kind == TypeKind.Struct) {
Debug.Assert(inst.Arguments.Count == inst.Method.Parameters.Count + 1);
// Transform call to struct constructor:
// call(ref, ...)
// => stobj(ref, newobj(...))
var newObj = new NewObj(inst.Method);
newObj.ILRange = inst.ILRange;
newObj.Arguments.AddRange(inst.Arguments.Skip(1));
@ -81,6 +83,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -81,6 +83,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
var expr = new StObj(inst.Arguments[0], result, inst.Method.DeclaringType);
inst.ReplaceWith(expr);
VisitStObj(expr);
} else {
base.VisitCall(inst);
}
}
protected internal override void VisitNewObj(NewObj inst)

6
ICSharpCode.Decompiler/IL/Transforms/InlineCompilerGeneratedVariables.cs

@ -57,8 +57,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -57,8 +57,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
bool CanInlineVariable(ILVariable v, IEnumerable<StLoc> stores, /*out*/ List<StLoc> storesToInline, /*out*/ List<StLoc> deadStores)
{
if (v.Kind != VariableKind.Local)
if (v.Kind != VariableKind.Local) {
return false;
}
if (v.HasInitialValue) {
return false; // cannot handle variables that are implicitly initialized at the beginning of the function
}
Debug.Assert(v.StoreCount == stores.Count());
// We expect there to be one store for every load,
// and potentially also some dead stores.

15
ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs → ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs

@ -17,22 +17,27 @@ @@ -17,22 +17,27 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.Decompiler.FlowAnalysis;
namespace ICSharpCode.Decompiler.IL.Transforms
{
/// <summary>
/// Split variables where possible.
/// Remove <c>HasInitialValue</c> from locals that are definitely assigned before every use
/// (=the initial value is a dead store).
/// </summary>
public class SplitVariables : IILTransform
public class RemoveDeadVariableInit : IILTransform
{
public void Run(ILFunction function, ILTransformContext context)
{
foreach (var container in function.Descendants.OfType<BlockContainer>()) {
container.SortBlocks();
var visitor = new DefiniteAssignmentVisitor(function);
function.Body.AcceptVisitor(visitor);
foreach (var v in function.Variables) {
if (v.Kind != VariableKind.Parameter && !visitor.IsPotentiallyUsedUninitialized(v)) {
v.HasInitialValue = false;
}
}
var rd = new ReachingDefinitions(function, v => v.Kind != VariableKind.StackSlot);
}
}
}

22
ICSharpCode.Decompiler/Util/BitSet.cs

@ -80,6 +80,16 @@ namespace ICSharpCode.Decompiler @@ -80,6 +80,16 @@ namespace ICSharpCode.Decompiler
bits.Or(other.bits);
}
public void IntersectWith(BitSet other)
{
bits.And(other.bits);
}
public void ClearAll()
{
bits.SetAll(false);
}
public void Clear(int index)
{
bits[index] = false;
@ -92,11 +102,23 @@ namespace ICSharpCode.Decompiler @@ -92,11 +102,23 @@ namespace ICSharpCode.Decompiler
}
}
public void SetAll()
{
bits.SetAll(true);
}
public void Set(int index)
{
bits[index] = true;
}
public void Set(int startIndex, int endIndex)
{
for (int i = startIndex; i < endIndex; i++) {
bits[i] = true;
}
}
public void ReplaceWith(BitSet incoming)
{
Debug.Assert(bits.Length == incoming.bits.Length);

4
ILSpy/Languages/CSharpLanguage.cs

@ -52,18 +52,16 @@ namespace ICSharpCode.ILSpy @@ -52,18 +52,16 @@ namespace ICSharpCode.ILSpy
#if DEBUG
internal static IEnumerable<CSharpLanguage> GetDebugLanguages()
{
//DecompilerContext context = new DecompilerContext(ModuleDefinition.CreateModule("dummy", ModuleKind.Dll));
var decompiler = new CSharpDecompiler(ModuleDefinition.CreateModule("Dummy", ModuleKind.Dll), new DecompilerSettings());
string lastTransformName = "no transforms";
int transformCount = 0;
foreach (var transform in decompiler.AstTransforms) {
Type transformType = transform.GetType(); // copy for lambda
yield return new CSharpLanguage {
transformCount = transformCount,
name = "C# - " + lastTransformName,
showAllMembers = true
};
lastTransformName = "after " + transformType.Name;
lastTransformName = "after " + transform.GetType().Name;
transformCount++;
}
yield return new CSharpLanguage {

Loading…
Cancel
Save