diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il
index 9c178a2ce..47ebea33a 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il
@@ -79,7 +79,34 @@ pointless:
box native int
call void [mscorlib]System.Console::WriteLine(string, object)
- /*
+ ldstr "Int32OrNativeReordered(0x7fffffff, false) = {0}"
+ ldc.i4 0x7fffffff
+ ldc.i4 0
+ call native int Program::Int32OrNativeReordered(int32, bool)
+ box native int
+ call void [mscorlib]System.Console::WriteLine(string, object)
+
+ ldstr "Int32OrNativeReordered(0x7fffffff, true) = {0}"
+ ldc.i4 0x7fffffff
+ ldc.i4 1
+ call native int Program::Int32OrNativeReordered(int32, bool)
+ box native int
+ call void [mscorlib]System.Console::WriteLine(string, object)
+
+ ldstr "Int32OrNativeReordered(-1, false) = {0}"
+ ldc.i4.m1
+ ldc.i4 0
+ call native int Program::Int32OrNativeReordered(int32, bool)
+ box native int
+ call void [mscorlib]System.Console::WriteLine(string, object)
+
+ ldstr "Int32OrNativeReordered(-1, true) = {0}"
+ ldc.i4.m1
+ ldc.i4 1
+ call native int Program::Int32OrNativeReordered(int32, bool)
+ box native int
+ call void [mscorlib]System.Console::WriteLine(string, object)
+
ldstr "Int32OrNativeLoopStyle(0x7fffffff):"
call void [mscorlib]System.Console::WriteLine(string)
ldc.i4 0x7fffffff
@@ -101,7 +128,6 @@ pointless:
call native int Program::Int32OrNativeDeadCode(int32)
box native int
call void [mscorlib]System.Console::WriteLine(string, object)
- */
ldc.i4 0x7fffffff
call void Program::RunInt32OrNativeMultiUse(int32)
@@ -127,7 +153,6 @@ pointless:
ret
}
- /*
.method public static native int Int32OrNativeReordered(int32 val, bool use_native)
{
// The spec is ambiguous whether the addition will be in 32-bits or native size.
@@ -187,7 +212,6 @@ pointless:
conv.u
br after_if
}
- */
.method public static void RunInt32OrNativeMultiUse(int32 val)
{
diff --git a/ICSharpCode.Decompiler/IL/BlockBuilder.cs b/ICSharpCode.Decompiler/IL/BlockBuilder.cs
index 75a7613bf..be06a5034 100644
--- a/ICSharpCode.Decompiler/IL/BlockBuilder.cs
+++ b/ICSharpCode.Decompiler/IL/BlockBuilder.cs
@@ -16,8 +16,8 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
+#nullable enable
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -29,6 +29,11 @@ using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL
{
+ ///
+ /// Converts the list of basic blocks from ILReader into a BlockContainer structure.
+ /// This involves creating nested block containers for exception handlers, and creating
+ /// branches between the blocks.
+ ///
class BlockBuilder
{
readonly MethodBodyBlock body;
@@ -64,7 +69,6 @@ namespace ICSharpCode.Decompiler.IL
var tryRange = new Interval(eh.TryOffset, eh.TryOffset + eh.TryLength);
var handlerBlock = new BlockContainer();
handlerBlock.AddILRange(new Interval(eh.HandlerOffset, eh.HandlerOffset + eh.HandlerLength));
- handlerBlock.Blocks.Add(new Block());
handlerContainers.Add(handlerBlock.StartILOffset, handlerBlock);
if (eh.Kind == ExceptionRegionKind.Fault || eh.Kind == ExceptionRegionKind.Finally)
@@ -94,7 +98,6 @@ namespace ICSharpCode.Decompiler.IL
{
var filterBlock = new BlockContainer(expectedResultType: StackType.I4);
filterBlock.AddILRange(new Interval(eh.FilterOffset, eh.HandlerOffset));
- filterBlock.Blocks.Add(new Block());
handlerContainers.Add(filterBlock.StartILOffset, filterBlock);
filter = filterBlock;
}
@@ -117,20 +120,18 @@ namespace ICSharpCode.Decompiler.IL
}
int currentTryIndex;
- TryInstruction nextTry;
+ TryInstruction? nextTry;
- BlockContainer currentContainer;
- Block currentBlock;
+ BlockContainer? currentContainer;
readonly Stack containerStack = new Stack();
- public void CreateBlocks(BlockContainer mainContainer, List instructions, BitSet incomingBranches, CancellationToken cancellationToken)
+ public void CreateBlocks(BlockContainer mainContainer, IEnumerable basicBlocks, CancellationToken cancellationToken)
{
CreateContainerStructure();
mainContainer.SetILRange(new Interval(0, body.GetCodeSize()));
- currentContainer = mainContainer;
- if (instructions.Count == 0)
+ if (!basicBlocks.Any())
{
- currentContainer.Blocks.Add(new Block {
+ mainContainer.Blocks.Add(new Block {
Instructions = {
new InvalidBranch("Empty body found. Decompiled assembly might be a reference assembly.")
}
@@ -138,99 +139,42 @@ namespace ICSharpCode.Decompiler.IL
return;
}
- foreach (var inst in instructions)
+ currentContainer = mainContainer;
+ foreach (var block in basicBlocks.OrderBy(b => b.StartILOffset))
{
cancellationToken.ThrowIfCancellationRequested();
- int start = inst.StartILOffset;
- if (currentBlock == null || (incomingBranches[start] && !IsStackAdjustment(inst)))
+ int start = block.StartILOffset;
+ // Leave nested containers if necessary
+ while (start >= currentContainer.EndILOffset)
{
- // Finish up the previous block
- FinalizeCurrentBlock(start, fallthrough: true);
- // Leave nested containers if necessary
- while (start >= currentContainer.EndILOffset)
- {
- currentContainer = containerStack.Pop();
- currentBlock = currentContainer.Blocks.Last();
- // this container is skipped (i.e. the loop will execute again)
- // set ILRange to the last instruction offset inside the block.
- if (start >= currentContainer.EndILOffset)
- {
- Debug.Assert(currentBlock.ILRangeIsEmpty);
- currentBlock.AddILRange(new Interval(currentBlock.StartILOffset, start));
- }
- }
- // Enter a handler if necessary
- if (handlerContainers.TryGetValue(start, out BlockContainer handlerContainer))
- {
- containerStack.Push(currentContainer);
- currentContainer = handlerContainer;
- currentBlock = handlerContainer.EntryPoint;
- }
- else
- {
- FinalizeCurrentBlock(start, fallthrough: false);
- // Create the new block
- currentBlock = new Block();
- currentContainer.Blocks.Add(currentBlock);
- }
- currentBlock.SetILRange(new Interval(start, start));
+ currentContainer = containerStack.Pop();
+ }
+ // Enter a handler if necessary
+ if (handlerContainers.TryGetValue(start, out BlockContainer? handlerContainer))
+ {
+ containerStack.Push(currentContainer);
+ currentContainer = handlerContainer;
}
+ // Enter a try block if necessary
while (nextTry != null && start == nextTry.TryBlock.StartILOffset)
{
- currentBlock.Instructions.Add(nextTry);
+ var blockForTry = new Block();
+ blockForTry.SetILRange(nextTry);
+ blockForTry.Instructions.Add(nextTry);
+ currentContainer.Blocks.Add(blockForTry);
+
containerStack.Push(currentContainer);
currentContainer = (BlockContainer)nextTry.TryBlock;
- currentBlock = new Block();
- currentContainer.Blocks.Add(currentBlock);
- currentBlock.SetILRange(new Interval(start, start));
nextTry = tryInstructionList.ElementAtOrDefault(++currentTryIndex);
}
- currentBlock.Instructions.Add(inst);
- if (inst.HasFlag(InstructionFlags.EndPointUnreachable))
- FinalizeCurrentBlock(inst.EndILOffset, fallthrough: false);
- else if (!CreateExtendedBlocks && inst.HasFlag(InstructionFlags.MayBranch))
- FinalizeCurrentBlock(inst.EndILOffset, fallthrough: true);
- }
- FinalizeCurrentBlock(mainContainer.EndILOffset, fallthrough: false);
- // Finish up all containers
- while (containerStack.Count > 0)
- {
- currentContainer = containerStack.Pop();
- currentBlock = currentContainer.Blocks.Last();
- FinalizeCurrentBlock(mainContainer.EndILOffset, fallthrough: false);
+ currentContainer.Blocks.Add(block);
}
+ Debug.Assert(currentTryIndex == tryInstructionList.Count && nextTry == null);
ConnectBranches(mainContainer, cancellationToken);
CreateOnErrorDispatchers();
}
- static bool IsStackAdjustment(ILInstruction inst)
- {
- return inst is StLoc stloc && stloc.IsStackAdjustment;
- }
-
- private void FinalizeCurrentBlock(int currentILOffset, bool fallthrough)
- {
- if (currentBlock == null)
- return;
- Debug.Assert(currentBlock.ILRangeIsEmpty);
- currentBlock.SetILRange(new Interval(currentBlock.StartILOffset, currentILOffset));
- if (fallthrough)
- {
- if (currentBlock.Instructions.LastOrDefault() is SwitchInstruction switchInst && switchInst.Sections.Last().Body.MatchNop())
- {
- // Instead of putting the default branch after the switch instruction
- switchInst.Sections.Last().Body = new Branch(currentILOffset);
- Debug.Assert(switchInst.HasFlag(InstructionFlags.EndPointUnreachable));
- }
- else
- {
- currentBlock.Instructions.Add(new Branch(currentILOffset));
- }
- }
- currentBlock = null;
- }
-
void ConnectBranches(ILInstruction inst, CancellationToken cancellationToken)
{
switch (inst)
@@ -238,12 +182,16 @@ namespace ICSharpCode.Decompiler.IL
case Branch branch:
cancellationToken.ThrowIfCancellationRequested();
Debug.Assert(branch.TargetBlock == null);
- branch.TargetBlock = FindBranchTarget(branch.TargetILOffset);
- if (branch.TargetBlock == null)
+ var targetBlock = FindBranchTarget(branch.TargetILOffset);
+ if (targetBlock == null)
{
branch.ReplaceWith(new InvalidBranch("Could not find block for branch target "
+ Disassembler.DisassemblerHelpers.OffsetToString(branch.TargetILOffset)).WithILRange(branch));
}
+ else
+ {
+ branch.TargetBlock = targetBlock;
+ }
break;
case Leave leave:
// ret (in void method) = leave(mainContainer)
@@ -279,7 +227,7 @@ namespace ICSharpCode.Decompiler.IL
}
}
- Block FindBranchTarget(int targetILOffset)
+ Block? FindBranchTarget(int targetILOffset)
{
foreach (var container in containerStack)
{
@@ -291,7 +239,7 @@ namespace ICSharpCode.Decompiler.IL
if (container.SlotInfo == TryCatchHandler.BodySlot)
{
// catch handler is allowed to branch back into try block (VB On Error)
- TryCatch tryCatch = (TryCatch)container.Parent.Parent;
+ TryCatch tryCatch = (TryCatch)container.Parent!.Parent!;
if (tryCatch.TryBlock.StartILOffset < targetILOffset && targetILOffset < tryCatch.TryBlock.EndILOffset)
{
return CreateBranchTargetForOnErrorJump(tryCatch, targetILOffset);
@@ -345,7 +293,7 @@ namespace ICSharpCode.Decompiler.IL
{
foreach (var (tryCatch, dispatch) in onErrorDispatchers)
{
- Block block = (Block)tryCatch.Parent;
+ Block block = (Block)tryCatch.Parent!;
// Before the regular entry point of the try-catch, insert an. instruction that resets the dispatcher variable
block.Instructions.Insert(tryCatch.ChildIndex, new StLoc(dispatch.Variable, new LdcI4(-1)));
// Split the block, so that we can introduce branches that jump directly into the try block
@@ -355,7 +303,7 @@ namespace ICSharpCode.Decompiler.IL
newBlock.Instructions.AddRange(block.Instructions.Skip(splitAt));
block.Instructions.RemoveRange(splitAt, block.Instructions.Count - splitAt);
block.Instructions.Add(new Branch(newBlock));
- ((BlockContainer)block.Parent).Blocks.Add(newBlock);
+ ((BlockContainer)block.Parent!).Blocks.Add(newBlock);
// Update the branches that jump directly into the try block
foreach (var b in dispatch.Branches)
{
diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs
index 154e65c5c..f53a56acc 100644
--- a/ICSharpCode.Decompiler/IL/ILReader.cs
+++ b/ICSharpCode.Decompiler/IL/ILReader.cs
@@ -46,6 +46,77 @@ namespace ICSharpCode.Decompiler.IL
///
public class ILReader
{
+ ///
+ /// Represents a block of IL instructions.
+ ///
+ private sealed class ImportedBlock
+ {
+ // These members are immediately available after construction:
+ public readonly Block Block = new Block(BlockKind.ControlFlow);
+ public ImmutableStack InputStack;
+
+ public int StartILOffset => Block.StartILOffset;
+ /// True if the import is in progress or completed.
+ public bool ImportStarted = false;
+
+ // When the block is imported, Block.Instructions is filled with the imported instructions
+ // and the following members are initialized:
+ public List<(ImportedBlock, ImmutableStack)> OutgoingEdges = new();
+
+ public ImportedBlock(int offset, ImmutableStack inputStack)
+ {
+ this.InputStack = inputStack;
+ this.Block.AddILRange(new Interval(offset, offset));
+ }
+
+ ///
+ /// Compares stack types and update InputStack if necessary.
+ /// Returns true if InputStack was updated, making a reimport necessary.
+ ///
+ public bool MergeStackTypes(ImmutableStack newEdge)
+ {
+ var a = this.InputStack;
+ var b = newEdge;
+ bool changed = false;
+ while (!a.IsEmpty && !b.IsEmpty)
+ {
+ if (a.Peek().StackType < b.Peek().StackType)
+ {
+ changed = true;
+ }
+ a = a.Pop();
+ b = b.Pop();
+ }
+ if (!changed || !(a.IsEmpty && b.IsEmpty))
+ return false;
+ a = this.InputStack;
+ b = newEdge;
+ var output = new List();
+ while (!a.IsEmpty && !b.IsEmpty)
+ {
+ if (a.Peek().StackType < b.Peek().StackType)
+ {
+ output.Add(b.Peek());
+ }
+ else
+ {
+ output.Add(a.Peek());
+ }
+ a = a.Pop();
+ b = b.Pop();
+ }
+ this.InputStack = ImmutableStack.CreateRange(output);
+ this.ImportStarted = false;
+ return true;
+ }
+
+ internal void ResetForReimport()
+ {
+ this.Block.Instructions.Clear();
+ this.OutgoingEdges.Clear();
+ }
+ }
+
readonly ICompilation compilation;
readonly MetadataModule module;
readonly MetadataReader metadata;
@@ -80,19 +151,17 @@ namespace ICSharpCode.Decompiler.IL
StackType methodReturnStackType;
BlobReader reader;
ImmutableStack currentStack = ImmutableStack.Empty;
+ ImportedBlock? currentBlock;
List expressionStack = new List();
ILVariable[] parameterVariables = null!;
ILVariable[] localVariables = null!;
BitSet isBranchTarget = null!;
BlockContainer mainContainer = null!;
- List instructionBuilder = new List();
int currentInstructionStart;
- // Dictionary that stores stacks for each IL instruction
- Dictionary> stackByOffset = new Dictionary>();
+ Dictionary blocksByOffset = new Dictionary();
+ Queue importQueue = new Queue();
Dictionary variableByExceptionHandler = new Dictionary();
- UnionFind unionFind = null!;
- List<(ILVariable, ILVariable)> stackMismatchPairs = new List<(ILVariable, ILVariable)>();
IEnumerable? stackVariables;
void Init(MethodDefinitionHandle methodDefinitionHandle, MethodBodyBlock body, GenericContext genericContext)
@@ -117,8 +186,6 @@ namespace ICSharpCode.Decompiler.IL
this.reader = body.GetILReader();
this.currentStack = ImmutableStack.Empty;
this.expressionStack.Clear();
- this.unionFind = new UnionFind();
- this.stackMismatchPairs.Clear();
this.methodReturnStackType = method.ReturnType.GetStackType();
InitParameterVariables();
localVariables = InitLocalVariables();
@@ -128,9 +195,9 @@ namespace ICSharpCode.Decompiler.IL
v.UsesInitialValue = true;
}
this.mainContainer = new BlockContainer(expectedResultType: methodReturnStackType);
- this.instructionBuilder.Clear();
+ this.blocksByOffset.Clear();
+ this.importQueue.Clear();
this.isBranchTarget = new BitSet(reader.Length);
- this.stackByOffset.Clear();
this.variableByExceptionHandler.Clear();
}
@@ -284,82 +351,71 @@ namespace ICSharpCode.Decompiler.IL
Warnings.Add(string.Format("IL_{0:x4}: {1}", currentInstructionStart, message));
}
- ImmutableStack MergeStacks(ImmutableStack a, ImmutableStack b)
+ ///
+ /// Check control flow edges for compatible stacks.
+ /// Returns union find data structure for unifying the different variables for the same stack slot.
+ /// Also inserts stack adjustments where necessary.
+ ///
+ UnionFind CheckOutgoingEdges()
{
- if (CheckStackCompatibleWithoutAdjustments(a, b))
- {
- // We only need to union the input variables, but can
- // otherwise re-use the existing stack.
- ImmutableStack output = a;
- while (!a.IsEmpty && !b.IsEmpty)
- {
- Debug.Assert(a.Peek().StackType == b.Peek().StackType);
- unionFind.Merge(a.Peek(), b.Peek());
- a = a.Pop();
- b = b.Pop();
- }
- return output;
- }
- else if (a.Count() != b.Count())
- {
- // Let's not try to merge mismatched stacks.
- Warn("Incompatible stack heights: " + a.Count() + " vs " + b.Count());
- return a;
- }
- else
+ var unionFind = new UnionFind();
+ foreach (var block in blocksByOffset.Values)
{
- // The more complex case where the stacks don't match exactly.
- var output = new List();
- while (!a.IsEmpty && !b.IsEmpty)
+ foreach (var (outgoing, stack) in block.OutgoingEdges)
{
- var varA = a.Peek();
- var varB = b.Peek();
- if (varA.StackType == varB.StackType)
+ var a = stack;
+ var b = outgoing.InputStack;
+ if (a.Count() != b.Count())
{
- unionFind.Merge(varA, varB);
- output.Add(varA);
+ // Let's not try to merge mismatched stacks.
+ Warnings.Add($"IL_{block.Block.EndILOffset:x4}->IL{outgoing.StartILOffset:x4}: Incompatible stack heights: {a.Count()} vs {b.Count()}");
+ continue;
}
- else
+ while (!a.IsEmpty && !b.IsEmpty)
{
- if (!IsValidTypeStackTypeMerge(varA.StackType, varB.StackType))
- {
- Warn("Incompatible stack types: " + varA.StackType + " vs " + varB.StackType);
- }
- if (varA.StackType > varB.StackType)
+ var varA = a.Peek();
+ var varB = b.Peek();
+ if (varA.StackType == varB.StackType)
{
- output.Add(varA);
- // every store to varB should also store to varA
- stackMismatchPairs.Add((varB, varA));
+ // The stack types match, so we can merge the variables.
+ unionFind.Merge(varA, varB);
}
else
{
- output.Add(varB);
- // every store to varA should also store to varB
- stackMismatchPairs.Add((varA, varB));
+ Debug.Assert(varA.StackType < varB.StackType);
+ if (!IsValidTypeStackTypeMerge(varA.StackType, varB.StackType))
+ {
+ Warnings.Add($"IL_{block.Block.EndILOffset:x4}->IL{outgoing.StartILOffset:x4}: Incompatible stack types: {varA.StackType} vs {varB.StackType}");
+ }
+ InsertStackAdjustment(block.Block, varA, varB);
}
+ a = a.Pop();
+ b = b.Pop();
}
- a = a.Pop();
- b = b.Pop();
}
- // because we built up output by popping from the input stacks, we need to reverse it to get back the original order
- output.Reverse();
- return ImmutableStack.CreateRange(output);
}
+ return unionFind;
}
- static bool CheckStackCompatibleWithoutAdjustments(ImmutableStack a, ImmutableStack b)
+ ///
+ /// Inserts a copy from varA to varB (with conversion) at the end of .
+ /// If the block ends with a branching instruction, the copy is inserted before the branching instruction.
+ ///
+ private void InsertStackAdjustment(Block block, ILVariable varA, ILVariable varB)
{
- while (!a.IsEmpty && !b.IsEmpty)
+ int insertionPosition = block.Instructions.Count;
+ while (insertionPosition > 0 && block.Instructions[insertionPosition - 1].HasFlag(InstructionFlags.MayBranch))
{
- if (a.Peek().StackType != b.Peek().StackType)
- return false;
- a = a.Pop();
- b = b.Pop();
+ // Branch instruction mustn't be initializing varA.
+ Debug.Assert(!block.Instructions[insertionPosition - 1].HasFlag(InstructionFlags.MayWriteLocals));
+ insertionPosition--;
}
- return a.IsEmpty && b.IsEmpty;
+ ILInstruction value = new LdLoc(varA);
+ value = new Conv(value, varB.StackType.ToPrimitiveType(), false, Sign.Signed);
+ block.Instructions.Insert(insertionPosition, new StLoc(varB, value) { IsStackAdjustment = true });
}
- private bool IsValidTypeStackTypeMerge(StackType stackType1, StackType stackType2)
+ private static bool IsValidTypeStackTypeMerge(StackType stackType1, StackType stackType2)
{
if (stackType1 == StackType.I && stackType2 == StackType.I4)
return true;
@@ -375,42 +431,66 @@ namespace ICSharpCode.Decompiler.IL
///
/// Stores the given stack for a branch to `offset`.
- ///
- /// The stack may be modified if stack adjustments are necessary. (e.g. implicit I4->I conversion)
///
- void StoreStackForOffset(int offset, ref ImmutableStack stack)
+ ImportedBlock StoreStackForOffset(int offset, ImmutableStack stack)
{
- if (stackByOffset.TryGetValue(offset, out var existing))
+ if (blocksByOffset.TryGetValue(offset, out var existing))
{
- stack = MergeStacks(existing, stack);
- if (stack != existing)
- stackByOffset[offset] = stack;
+ bool wasImported = existing.ImportStarted;
+ if (existing.MergeStackTypes(stack) && wasImported)
+ {
+ // If the stack changed, we need to re-import the block.
+ importQueue.Enqueue(existing);
+ }
+ return existing;
}
else
{
- stackByOffset.Add(offset, stack);
+ ImportedBlock newBlock = new ImportedBlock(offset, stack);
+ blocksByOffset.Add(offset, newBlock);
+ importQueue.Enqueue(newBlock);
+ return newBlock;
}
}
void ReadInstructions(CancellationToken cancellationToken)
{
reader.Reset();
+ StoreStackForOffset(0, ImmutableStack.Empty);
ILParser.SetBranchTargets(ref reader, isBranchTarget);
- reader.Reset();
PrepareBranchTargetsAndStacksForExceptionHandlers();
- bool nextInstructionBeginsNewBlock = false;
+ // Import of IL byte codes:
+ while (importQueue.Count > 0)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ ImportedBlock block = importQueue.Dequeue();
+ ReadBlock(block, cancellationToken);
+ }
- reader.Reset();
+ // Merge different variables for same stack slot:
+ var unionFind = CheckOutgoingEdges();
+ var visitor = new CollectStackVariablesVisitor(unionFind);
+ foreach (var block in blocksByOffset.Values)
+ {
+ block.Block.AcceptVisitor(visitor);
+ }
+ stackVariables = visitor.variables;
+ }
+
+ void ReadBlock(ImportedBlock block, CancellationToken cancellationToken)
+ {
+ Debug.Assert(!block.ImportStarted);
+ block.ResetForReimport();
+ block.ImportStarted = true;
+ reader.Offset = block.StartILOffset;
+ currentBlock = block;
+ currentStack = block.InputStack;
+ // Read instructions until we reach the end of the block.
while (reader.RemainingBytes > 0)
{
cancellationToken.ThrowIfCancellationRequested();
int start = reader.Offset;
- if (isBranchTarget[start])
- {
- FlushExpressionStack();
- StoreStackForOffset(start, ref currentStack);
- }
currentInstructionStart = start;
bool startedWithEmptyStack = CurrentStackIsEmpty();
DecodedInstruction decodedInstruction;
@@ -432,9 +512,9 @@ namespace ICSharpCode.Decompiler.IL
{
// Flush to avoid re-ordering of side-effects
FlushExpressionStack();
- instructionBuilder.Add(inst);
+ block.Block.Instructions.Add(inst);
}
- else if (isBranchTarget[start] || nextInstructionBeginsNewBlock)
+ else if (start == block.StartILOffset)
{
// If this instruction is the first in a new block, avoid it being inlined
// into the next instruction.
@@ -442,39 +522,39 @@ namespace ICSharpCode.Decompiler.IL
// detect block starts, and doesn't search nested instructions.
FlushExpressionStack();
}
- if (inst.HasDirectFlag(InstructionFlags.EndPointUnreachable))
+
+ if ((!decodedInstruction.PushedOnExpressionStack && IsSequencePointInstruction(inst)) || startedWithEmptyStack)
{
- FlushExpressionStack();
- if (stackByOffset.TryGetValue(end, out var stackAfterEnd))
- {
- currentStack = stackAfterEnd;
- }
- else
- {
- currentStack = ImmutableStack.Empty;
- }
- nextInstructionBeginsNewBlock = true;
+ this.SequencePointCandidates.Add(inst.StartILOffset);
}
- else
+
+ if (inst.HasDirectFlag(InstructionFlags.EndPointUnreachable))
{
- nextInstructionBeginsNewBlock = inst.HasFlag(InstructionFlags.MayBranch);
+ break; // end of block, don't parse following instructions if they are unreachable
}
-
- if ((!decodedInstruction.PushedOnExpressionStack && IsSequencePointInstruction(inst)) || startedWithEmptyStack)
+ else if (isBranchTarget[end] || inst.HasFlag(InstructionFlags.MayBranch))
{
- this.SequencePointCandidates.Add(inst.StartILOffset);
+ break; // end of block (we'll create fall through)
}
}
-
+ // Finalize block
FlushExpressionStack();
-
- var visitor = new CollectStackVariablesVisitor(unionFind);
- for (int i = 0; i < instructionBuilder.Count; i++)
+ block.Block.AddILRange(new Interval(block.StartILOffset, reader.Offset));
+ if (!block.Block.HasFlag(InstructionFlags.EndPointUnreachable))
{
- instructionBuilder[i] = instructionBuilder[i].AcceptVisitor(visitor);
+ // create fall through branch
+ MarkBranchTarget(reader.Offset, isFallThrough: true);
+ if (block.Block.Instructions.LastOrDefault() is SwitchInstruction switchInst && switchInst.Sections.Last().Body.MatchNop())
+ {
+ // Instead of putting the default branch after the switch instruction
+ switchInst.Sections.Last().Body = new Branch(reader.Offset);
+ Debug.Assert(switchInst.HasFlag(InstructionFlags.EndPointUnreachable));
+ }
+ else
+ {
+ block.Block.Instructions.Add(new Branch(reader.Offset));
+ }
}
- stackVariables = visitor.variables;
- InsertStackAdjustments();
}
private bool CurrentStackIsEmpty()
@@ -488,9 +568,7 @@ namespace ICSharpCode.Decompiler.IL
foreach (var eh in body.ExceptionRegions)
{
// Always mark the start of the try block as a "branch target".
- // We don't actually need to store the stack state here,
- // but we need to ensure that no ILInstructions are inlined
- // into the try-block.
+ // We need to ensure that we put a block boundary there.
isBranchTarget[eh.TryOffset] = true;
ImmutableStack ehStack;
@@ -520,25 +598,23 @@ namespace ICSharpCode.Decompiler.IL
if (eh.FilterOffset != -1)
{
isBranchTarget[eh.FilterOffset] = true;
- StoreStackForOffset(eh.FilterOffset, ref ehStack);
+ StoreStackForOffset(eh.FilterOffset, ehStack);
}
if (eh.HandlerOffset != -1)
{
isBranchTarget[eh.HandlerOffset] = true;
- StoreStackForOffset(eh.HandlerOffset, ref ehStack);
+ StoreStackForOffset(eh.HandlerOffset, ehStack);
}
}
}
- private bool IsSequencePointInstruction(ILInstruction instruction)
+ private static bool IsSequencePointInstruction(ILInstruction instruction)
{
- if (instruction.OpCode == OpCode.Nop ||
- (instructionBuilder.Count > 0
- && instructionBuilder.Last().OpCode is OpCode.Call
- or OpCode.CallIndirect
- or OpCode.CallVirt))
+ if (instruction.OpCode is OpCode.Nop
+ or OpCode.Call
+ or OpCode.CallIndirect
+ or OpCode.CallVirt)
{
-
return true;
}
else
@@ -547,39 +623,6 @@ namespace ICSharpCode.Decompiler.IL
}
}
- void InsertStackAdjustments()
- {
- if (stackMismatchPairs.Count == 0)
- return;
- var dict = new MultiDictionary();
- foreach (var (origA, origB) in stackMismatchPairs)
- {
- var a = unionFind.Find(origA);
- var b = unionFind.Find(origB);
- Debug.Assert(a.StackType < b.StackType);
- // For every store to a, insert a converting store to b.
- if (!dict[a].Contains(b))
- dict.Add(a, b);
- }
- var newInstructions = new List();
- foreach (var inst in instructionBuilder)
- {
- newInstructions.Add(inst);
- if (inst is StLoc store)
- {
- foreach (var additionalVar in dict[store.Variable])
- {
- ILInstruction value = new LdLoc(store.Variable);
- value = new Conv(value, additionalVar.StackType.ToPrimitiveType(), false, Sign.Signed);
- newInstructions.Add(new StLoc(additionalVar, value) {
- IsStackAdjustment = true,
- }.WithILRange(inst));
- }
- }
- }
- instructionBuilder = newInstructions;
- }
-
///
/// Debugging helper: writes the decoded instruction stream interleaved with the inferred evaluation stack layout.
///
@@ -588,39 +631,24 @@ namespace ICSharpCode.Decompiler.IL
{
Init(method, body, genericContext);
ReadInstructions(cancellationToken);
- foreach (var inst in instructionBuilder)
+ foreach (var importBlock in blocksByOffset.Values.OrderBy(b => b.StartILOffset))
{
- if (inst is StLoc stloc && stloc.IsStackAdjustment)
- {
- output.Write(" ");
- inst.WriteTo(output, new ILAstWritingOptions());
- output.WriteLine();
- continue;
- }
- if (stackByOffset.TryGetValue(inst.StartILOffset, out var stack))
+ output.Write(" [");
+ bool isFirstElement = true;
+ foreach (var element in importBlock.InputStack)
{
- output.Write(" [");
- bool isFirstElement = true;
- foreach (var element in stack)
- {
- if (isFirstElement)
- isFirstElement = false;
- else
- output.Write(", ");
- output.WriteLocalReference(element.Name, element);
- output.Write(":");
- output.Write(element.StackType);
- }
- output.Write(']');
- output.WriteLine();
+ if (isFirstElement)
+ isFirstElement = false;
+ else
+ output.Write(", ");
+ output.WriteLocalReference(element.Name, element);
+ output.Write(":");
+ output.Write(element.StackType);
}
- if (isBranchTarget[inst.StartILOffset])
- output.Write('*');
- else
- output.Write(' ');
- output.WriteLocalReference("IL_" + inst.StartILOffset.ToString("x4"), inst.StartILOffset, isDefinition: true);
- output.Write(": ");
- inst.WriteTo(output, new ILAstWritingOptions());
+ output.Write(']');
+ output.WriteLine();
+
+ importBlock.Block.WriteTo(output, new ILAstWritingOptions());
output.WriteLine();
}
new Disassembler.MethodBodyDisassembler(output, cancellationToken) { DetectControlStructure = false }
@@ -636,7 +664,7 @@ namespace ICSharpCode.Decompiler.IL
Init(method, body, genericContext);
ReadInstructions(cancellationToken);
var blockBuilder = new BlockBuilder(body, variableByExceptionHandler, compilation);
- blockBuilder.CreateBlocks(mainContainer, instructionBuilder, isBranchTarget, cancellationToken);
+ blockBuilder.CreateBlocks(mainContainer, blocksByOffset.Values.Select(ib => ib.Block), cancellationToken);
var function = new ILFunction(this.method, body.GetCodeSize(), this.genericContext, mainContainer, kind);
function.Variables.AddRange(parameterVariables);
function.Variables.AddRange(localVariables);
@@ -1187,7 +1215,7 @@ namespace ICSharpCode.Decompiler.IL
return currentStack.Peek().StackType;
}
- class CollectStackVariablesVisitor : ILVisitor
+ sealed class CollectStackVariablesVisitor : ILVisitor
{
readonly UnionFind unionFind;
internal readonly HashSet variables = new HashSet();
@@ -1216,7 +1244,7 @@ namespace ICSharpCode.Decompiler.IL
{
var variable = unionFind.Find(inst.Variable);
if (variables.Add(variable))
- variable.Name = "S_" + (variables.Count - 1);
+ variable.Name = $"S_{variables.Count - 1}";
return new LdLoc(variable).WithILRange(inst);
}
return inst;
@@ -1229,7 +1257,7 @@ namespace ICSharpCode.Decompiler.IL
{
var variable = unionFind.Find(inst.Variable);
if (variables.Add(variable))
- variable.Name = "S_" + (variables.Count - 1);
+ variable.Name = $"S_{variables.Count - 1}";
return new StLoc(variable, inst.Value).WithILRange(inst);
}
return inst;
@@ -1296,7 +1324,7 @@ namespace ICSharpCode.Decompiler.IL
return Cast(inst, expectedType, Warnings, reader.Offset);
}
- internal static ILInstruction Cast(ILInstruction inst, StackType expectedType, List warnings, int ilOffset)
+ internal static ILInstruction Cast(ILInstruction inst, StackType expectedType, List? warnings, int ilOffset)
{
if (expectedType != inst.ResultType)
{
@@ -1913,11 +1941,13 @@ namespace ICSharpCode.Decompiler.IL
}
}
- void MarkBranchTarget(int targetILOffset)
+ void MarkBranchTarget(int targetILOffset, bool isFallThrough = false)
{
FlushExpressionStack();
- Debug.Assert(isBranchTarget[targetILOffset]);
- StoreStackForOffset(targetILOffset, ref currentStack);
+ Debug.Assert(isFallThrough || isBranchTarget[targetILOffset]);
+ var targetBlock = StoreStackForOffset(targetILOffset, currentStack);
+ Debug.Assert(currentBlock != null);
+ currentBlock.OutgoingEdges.Add((targetBlock, currentStack));
}
///
@@ -1928,6 +1958,7 @@ namespace ICSharpCode.Decompiler.IL
///
private void FlushExpressionStack()
{
+ Debug.Assert(currentBlock != null);
foreach (var inst in expressionStack)
{
Debug.Assert(inst.ResultType != StackType.Void);
@@ -1935,7 +1966,7 @@ namespace ICSharpCode.Decompiler.IL
var v = new ILVariable(VariableKind.StackSlot, type, inst.ResultType);
v.HasGeneratedName = true;
currentStack = currentStack.Push(v);
- instructionBuilder.Add(new StLoc(v, inst).WithILRange(inst));
+ currentBlock.Block.Instructions.Add(new StLoc(v, inst).WithILRange(inst));
}
expressionStack.Clear();
}