Browse Source

ILReader: don't create stack slots if we can directly created inlined ILAst

This is a performance optimization: we dramatically reduce the amount of ILVariables created;
and thus need to spend less time in the first ILInlining run.
pull/2766/head
Daniel Grunwald 3 years ago committed by Siegfried Pammer
parent
commit
c36c6efb2a
  1. 167
      ICSharpCode.Decompiler/IL/ILReader.cs

167
ICSharpCode.Decompiler/IL/ILReader.cs

@ -78,6 +78,7 @@ namespace ICSharpCode.Decompiler.IL
StackType methodReturnStackType; StackType methodReturnStackType;
BlobReader reader; BlobReader reader;
ImmutableStack<ILVariable> currentStack; ImmutableStack<ILVariable> currentStack;
List<ILInstruction> expressionStack;
ILVariable[] parameterVariables; ILVariable[] parameterVariables;
ILVariable[] localVariables; ILVariable[] localVariables;
BitSet isBranchTarget; BitSet isBranchTarget;
@ -113,6 +114,7 @@ namespace ICSharpCode.Decompiler.IL
this.body = body; this.body = body;
this.reader = body.GetILReader(); this.reader = body.GetILReader();
this.currentStack = ImmutableStack<ILVariable>.Empty; this.currentStack = ImmutableStack<ILVariable>.Empty;
this.expressionStack = new List<ILInstruction>();
this.unionFind = new UnionFind<ILVariable>(); this.unionFind = new UnionFind<ILVariable>();
this.stackMismatchPairs = new List<(ILVariable, ILVariable)>(); this.stackMismatchPairs = new List<(ILVariable, ILVariable)>();
this.methodReturnStackType = method.ReturnType.GetStackType(); this.methodReturnStackType = method.ReturnType.GetStackType();
@ -395,15 +397,21 @@ namespace ICSharpCode.Decompiler.IL
reader.Reset(); reader.Reset();
PrepareBranchTargetsAndStacksForExceptionHandlers(); PrepareBranchTargetsAndStacksForExceptionHandlers();
bool nextInstructionBeginsNewBlock = false;
reader.Reset(); reader.Reset();
while (reader.RemainingBytes > 0) while (reader.RemainingBytes > 0)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
int start = reader.Offset; int start = reader.Offset;
if (isBranchTarget[start])
{
FlushExpressionStack();
StoreStackForOffset(start, ref currentStack); StoreStackForOffset(start, ref currentStack);
}
currentInstructionStart = start; currentInstructionStart = start;
bool startedWithEmptyStack = currentStack.IsEmpty; bool startedWithEmptyStack = CurrentStackIsEmpty();
ILInstruction decodedInstruction; DecodedInstruction decodedInstruction;
try try
{ {
decodedInstruction = DecodeInstruction(); decodedInstruction = DecodeInstruction();
@ -412,27 +420,48 @@ namespace ICSharpCode.Decompiler.IL
{ {
decodedInstruction = new InvalidBranch(ex.Message); decodedInstruction = new InvalidBranch(ex.Message);
} }
if (decodedInstruction.ResultType == StackType.Unknown && decodedInstruction.OpCode != OpCode.InvalidBranch && UnpackPush(decodedInstruction).OpCode != OpCode.InvalidExpression) var inst = decodedInstruction.Instruction;
if (inst.ResultType == StackType.Unknown && inst.OpCode != OpCode.InvalidBranch && inst.OpCode != OpCode.InvalidExpression)
Warn("Unknown result type (might be due to invalid IL or missing references)"); Warn("Unknown result type (might be due to invalid IL or missing references)");
decodedInstruction.CheckInvariant(ILPhase.InILReader); inst.CheckInvariant(ILPhase.InILReader);
int end = reader.Offset; int end = reader.Offset;
decodedInstruction.AddILRange(new Interval(start, end)); inst.AddILRange(new Interval(start, end));
UnpackPush(decodedInstruction).AddILRange(decodedInstruction); if (!decodedInstruction.PushedOnExpressionStack)
instructionBuilder.Add(decodedInstruction); {
if (decodedInstruction.HasDirectFlag(InstructionFlags.EndPointUnreachable)) // Flush to avoid re-ordering of side-effects
FlushExpressionStack();
instructionBuilder.Add(inst);
}
else if (isBranchTarget[start] || nextInstructionBeginsNewBlock)
{
// If this instruction is the first in a new block, avoid it being inlined
// into the next instruction.
// This is necessary because the BlockBuilder uses inst.StartILOffset to
// detect block starts, and doesn't search nested instructions.
FlushExpressionStack();
}
if (inst.HasDirectFlag(InstructionFlags.EndPointUnreachable))
{ {
FlushExpressionStack();
if (!stackByOffset.TryGetValue(end, out currentStack)) if (!stackByOffset.TryGetValue(end, out currentStack))
{ {
currentStack = ImmutableStack<ILVariable>.Empty; currentStack = ImmutableStack<ILVariable>.Empty;
} }
nextInstructionBeginsNewBlock = true;
}
else
{
nextInstructionBeginsNewBlock = inst.HasFlag(InstructionFlags.MayBranch);
} }
if (IsSequencePointInstruction(decodedInstruction) || startedWithEmptyStack) if ((!decodedInstruction.PushedOnExpressionStack && IsSequencePointInstruction(inst)) || startedWithEmptyStack)
{ {
this.SequencePointCandidates.Add(decodedInstruction.StartILOffset); this.SequencePointCandidates.Add(inst.StartILOffset);
} }
} }
FlushExpressionStack();
var visitor = new CollectStackVariablesVisitor(unionFind); var visitor = new CollectStackVariablesVisitor(unionFind);
for (int i = 0; i < instructionBuilder.Count; i++) for (int i = 0; i < instructionBuilder.Count; i++)
{ {
@ -442,11 +471,22 @@ namespace ICSharpCode.Decompiler.IL
InsertStackAdjustments(); InsertStackAdjustments();
} }
private bool CurrentStackIsEmpty()
{
return currentStack.IsEmpty && expressionStack.Count == 0;
}
private void PrepareBranchTargetsAndStacksForExceptionHandlers() private void PrepareBranchTargetsAndStacksForExceptionHandlers()
{ {
// Fill isBranchTarget and branchStackDict based on exception handlers // Fill isBranchTarget and branchStackDict based on exception handlers
foreach (var eh in body.ExceptionRegions) 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.
isBranchTarget[eh.TryOffset] = true;
ImmutableStack<ILVariable> ehStack; ImmutableStack<ILVariable> ehStack;
if (eh.Kind == ExceptionRegionKind.Catch) if (eh.Kind == ExceptionRegionKind.Catch)
{ {
@ -551,9 +591,11 @@ namespace ICSharpCode.Decompiler.IL
output.WriteLine(); output.WriteLine();
continue; continue;
} }
if (stackByOffset.TryGetValue(inst.StartILOffset, out var stack))
{
output.Write(" ["); output.Write(" [");
bool isFirstElement = true; bool isFirstElement = true;
foreach (var element in stackByOffset[inst.StartILOffset]) foreach (var element in stack)
{ {
if (isFirstElement) if (isFirstElement)
isFirstElement = false; isFirstElement = false;
@ -565,6 +607,7 @@ namespace ICSharpCode.Decompiler.IL
} }
output.Write(']'); output.Write(']');
output.WriteLine(); output.WriteLine();
}
if (isBranchTarget[inst.StartILOffset]) if (isBranchTarget[inst.StartILOffset])
output.Write('*'); output.Write('*');
else else
@ -619,17 +662,7 @@ namespace ICSharpCode.Decompiler.IL
return function; return function;
} }
static ILInstruction UnpackPush(ILInstruction inst) DecodedInstruction Neg()
{
ILVariable v;
ILInstruction inner;
if (inst.MatchStLoc(out v, out inner) && v.Kind == VariableKind.StackSlot)
return inner;
else
return inst;
}
ILInstruction Neg()
{ {
switch (PeekStackType()) switch (PeekStackType())
{ {
@ -649,7 +682,18 @@ namespace ICSharpCode.Decompiler.IL
} }
} }
ILInstruction DecodeInstruction() struct DecodedInstruction
{
public ILInstruction Instruction;
public bool PushedOnExpressionStack;
public static implicit operator DecodedInstruction(ILInstruction instruction)
{
return new DecodedInstruction { Instruction = instruction };
}
}
DecodedInstruction DecodeInstruction()
{ {
if (reader.RemainingBytes == 0) if (reader.RemainingBytes == 0)
return new InvalidBranch("Unexpected end of body"); return new InvalidBranch("Unexpected end of body");
@ -939,6 +983,7 @@ namespace ICSharpCode.Decompiler.IL
case ILOpCode.Or: case ILOpCode.Or:
return BinaryNumeric(BinaryNumericOperator.BitOr); return BinaryNumeric(BinaryNumericOperator.BitOr);
case ILOpCode.Pop: case ILOpCode.Pop:
FlushExpressionStack();
Pop(); Pop();
return new Nop() { Kind = NopKind.Pop }; return new Nop() { Kind = NopKind.Pop };
case ILOpCode.Rem: case ILOpCode.Rem:
@ -1119,6 +1164,8 @@ namespace ICSharpCode.Decompiler.IL
StackType PeekStackType() StackType PeekStackType()
{ {
if (expressionStack.Count > 0)
return expressionStack.Last().ResultType;
if (currentStack.IsEmpty) if (currentStack.IsEmpty)
return StackType.Unknown; return StackType.Unknown;
else else
@ -1174,18 +1221,18 @@ namespace ICSharpCode.Decompiler.IL
} }
} }
ILInstruction Push(ILInstruction inst) DecodedInstruction Push(ILInstruction inst)
{ {
Debug.Assert(inst.ResultType != StackType.Void); expressionStack.Add(inst);
IType type = compilation.FindType(inst.ResultType); return new DecodedInstruction {
var v = new ILVariable(VariableKind.StackSlot, type, inst.ResultType); Instruction = inst,
v.HasGeneratedName = true; PushedOnExpressionStack = true
currentStack = currentStack.Push(v); };
return new StLoc(v, inst);
} }
ILInstruction Peek() ILInstruction Peek()
{ {
FlushExpressionStack();
if (currentStack.IsEmpty) if (currentStack.IsEmpty)
{ {
return new InvalidExpression("Stack underflow").WithILRange(new Interval(reader.Offset, reader.Offset)); return new InvalidExpression("Stack underflow").WithILRange(new Interval(reader.Offset, reader.Offset));
@ -1195,6 +1242,12 @@ namespace ICSharpCode.Decompiler.IL
ILInstruction Pop() ILInstruction Pop()
{ {
if (expressionStack.Count > 0)
{
var inst = expressionStack.Last();
expressionStack.RemoveAt(expressionStack.Count - 1);
return inst;
}
if (currentStack.IsEmpty) if (currentStack.IsEmpty)
{ {
return new InvalidExpression("Stack underflow").WithILRange(new Interval(reader.Offset, reader.Offset)); return new InvalidExpression("Stack underflow").WithILRange(new Interval(reader.Offset, reader.Offset));
@ -1396,6 +1449,7 @@ namespace ICSharpCode.Decompiler.IL
} }
else else
{ {
FlushExpressionStack();
Pop(); Pop();
return new InvalidExpression($"starg {v} (out-of-bounds)"); return new InvalidExpression($"starg {v} (out-of-bounds)");
} }
@ -1430,17 +1484,18 @@ namespace ICSharpCode.Decompiler.IL
if (v >= 0 && v < localVariables.Length) if (v >= 0 && v < localVariables.Length)
{ {
return new StLoc(localVariables[v], Pop(localVariables[v].StackType)) { return new StLoc(localVariables[v], Pop(localVariables[v].StackType)) {
ILStackWasEmpty = currentStack.IsEmpty ILStackWasEmpty = CurrentStackIsEmpty()
}; };
} }
else else
{ {
FlushExpressionStack();
Pop(); Pop();
return new InvalidExpression($"stloc {v} (out-of-bounds)"); return new InvalidExpression($"stloc {v} (out-of-bounds)");
} }
} }
private ILInstruction LdElem(IType type) private DecodedInstruction LdElem(IType type)
{ {
return Push(new LdObj(new LdElema(indices: Pop(), array: Pop(), type: type) { DelayExceptions = true }, type)); return Push(new LdObj(new LdElema(indices: Pop(), array: Pop(), type: type) { DelayExceptions = true }, type));
} }
@ -1456,17 +1511,17 @@ namespace ICSharpCode.Decompiler.IL
ILInstruction InitObj(ILInstruction target, IType type) ILInstruction InitObj(ILInstruction target, IType type)
{ {
var value = new DefaultValue(type); var value = new DefaultValue(type);
value.ILStackWasEmpty = currentStack.IsEmpty; value.ILStackWasEmpty = CurrentStackIsEmpty();
return new StObj(target, value, type); return new StObj(target, value, type);
} }
IType constrainedPrefix; IType constrainedPrefix;
private ILInstruction DecodeConstrainedCall() private DecodedInstruction DecodeConstrainedCall()
{ {
constrainedPrefix = ReadAndDecodeTypeReference(); constrainedPrefix = ReadAndDecodeTypeReference();
var inst = DecodeInstruction(); var inst = DecodeInstruction();
var call = UnpackPush(inst) as CallInstruction; var call = inst.Instruction as CallInstruction;
if (call != null) if (call != null)
Debug.Assert(call.ConstrainedTo == constrainedPrefix); Debug.Assert(call.ConstrainedTo == constrainedPrefix);
else else
@ -1475,10 +1530,10 @@ namespace ICSharpCode.Decompiler.IL
return inst; return inst;
} }
private ILInstruction DecodeTailCall() private DecodedInstruction DecodeTailCall()
{ {
var inst = DecodeInstruction(); var inst = DecodeInstruction();
var call = UnpackPush(inst) as CallInstruction; var call = inst.Instruction as CallInstruction;
if (call != null) if (call != null)
call.IsTail = true; call.IsTail = true;
else else
@ -1486,11 +1541,11 @@ namespace ICSharpCode.Decompiler.IL
return inst; return inst;
} }
private ILInstruction DecodeUnaligned() private DecodedInstruction DecodeUnaligned()
{ {
byte alignment = reader.ReadByte(); byte alignment = reader.ReadByte();
var inst = DecodeInstruction(); var inst = DecodeInstruction();
var sup = UnpackPush(inst) as ISupportsUnalignedPrefix; var sup = inst.Instruction as ISupportsUnalignedPrefix;
if (sup != null) if (sup != null)
sup.UnalignedPrefix = alignment; sup.UnalignedPrefix = alignment;
else else
@ -1498,10 +1553,10 @@ namespace ICSharpCode.Decompiler.IL
return inst; return inst;
} }
private ILInstruction DecodeVolatile() private DecodedInstruction DecodeVolatile()
{ {
var inst = DecodeInstruction(); var inst = DecodeInstruction();
var svp = UnpackPush(inst) as ISupportsVolatilePrefix; var svp = inst.Instruction as ISupportsVolatilePrefix;
if (svp != null) if (svp != null)
svp.IsVolatile = true; svp.IsVolatile = true;
else else
@ -1509,10 +1564,10 @@ namespace ICSharpCode.Decompiler.IL
return inst; return inst;
} }
private ILInstruction DecodeReadonly() private DecodedInstruction DecodeReadonly()
{ {
var inst = DecodeInstruction(); var inst = DecodeInstruction();
var ldelema = UnpackPush(inst) as LdElema; var ldelema = inst.Instruction as LdElema;
if (ldelema != null) if (ldelema != null)
ldelema.IsReadOnly = true; ldelema.IsReadOnly = true;
else else
@ -1520,7 +1575,7 @@ namespace ICSharpCode.Decompiler.IL
return inst; return inst;
} }
ILInstruction DecodeCall(OpCode opCode) DecodedInstruction DecodeCall(OpCode opCode)
{ {
var method = ReadAndDecodeMethodReference(); var method = ReadAndDecodeMethodReference();
int firstArgument = (opCode != OpCode.NewObj && !method.IsStatic) ? 1 : 0; int firstArgument = (opCode != OpCode.NewObj && !method.IsStatic) ? 1 : 0;
@ -1571,14 +1626,14 @@ namespace ICSharpCode.Decompiler.IL
// This needs to happen early (not as a transform) because the StObj.TargetSlot has // This needs to happen early (not as a transform) because the StObj.TargetSlot has
// restricted inlining (doesn't accept ldflda when exceptions aren't delayed). // restricted inlining (doesn't accept ldflda when exceptions aren't delayed).
var newobj = new NewObj(method); var newobj = new NewObj(method);
newobj.ILStackWasEmpty = currentStack.IsEmpty; newobj.ILStackWasEmpty = CurrentStackIsEmpty();
newobj.ConstrainedTo = constrainedPrefix; newobj.ConstrainedTo = constrainedPrefix;
newobj.Arguments.AddRange(arguments.Skip(1)); newobj.Arguments.AddRange(arguments.Skip(1));
return new StObj(arguments[0], newobj, method.DeclaringType); return new StObj(arguments[0], newobj, method.DeclaringType);
} }
default: default:
var call = CallInstruction.Create(opCode, method); var call = CallInstruction.Create(opCode, method);
call.ILStackWasEmpty = currentStack.IsEmpty; call.ILStackWasEmpty = CurrentStackIsEmpty();
call.ConstrainedTo = constrainedPrefix; call.ConstrainedTo = constrainedPrefix;
call.Arguments.AddRange(arguments); call.Arguments.AddRange(arguments);
if (call.ResultType != StackType.Void) if (call.ResultType != StackType.Void)
@ -1587,7 +1642,7 @@ namespace ICSharpCode.Decompiler.IL
} }
} }
ILInstruction DecodeCallIndirect() DecodedInstruction DecodeCallIndirect()
{ {
var signatureHandle = (StandaloneSignatureHandle)ReadAndDecodeMetadataToken(); var signatureHandle = (StandaloneSignatureHandle)ReadAndDecodeMetadataToken();
var (header, fpt) = module.DecodeMethodSignature(signatureHandle, genericContext); var (header, fpt) = module.DecodeMethodSignature(signatureHandle, genericContext);
@ -1780,6 +1835,7 @@ namespace ICSharpCode.Decompiler.IL
int target = ILParser.DecodeBranchTarget(ref reader, opCode); int target = ILParser.DecodeBranchTarget(ref reader, opCode);
if (isLeave) if (isLeave)
{ {
FlushExpressionStack();
currentStack = currentStack.Clear(); currentStack = currentStack.Clear();
} }
if (!IsInvalidBranch(target)) if (!IsInvalidBranch(target))
@ -1795,10 +1851,25 @@ namespace ICSharpCode.Decompiler.IL
void MarkBranchTarget(int targetILOffset) void MarkBranchTarget(int targetILOffset)
{ {
FlushExpressionStack();
Debug.Assert(isBranchTarget[targetILOffset]); Debug.Assert(isBranchTarget[targetILOffset]);
StoreStackForOffset(targetILOffset, ref currentStack); StoreStackForOffset(targetILOffset, ref currentStack);
} }
private void FlushExpressionStack()
{
foreach (var inst in expressionStack)
{
Debug.Assert(inst.ResultType != StackType.Void);
IType type = compilation.FindType(inst.ResultType);
var v = new ILVariable(VariableKind.StackSlot, type, inst.ResultType);
v.HasGeneratedName = true;
currentStack = currentStack.Push(v);
instructionBuilder.Add(new StLoc(v, inst).WithILRange(inst));
}
expressionStack.Clear();
}
ILInstruction DecodeSwitch() ILInstruction DecodeSwitch()
{ {
var targets = ILParser.DecodeSwitchTargets(ref reader); var targets = ILParser.DecodeSwitchTargets(ref reader);
@ -1827,7 +1898,7 @@ namespace ICSharpCode.Decompiler.IL
return instr; return instr;
} }
ILInstruction BinaryNumeric(BinaryNumericOperator @operator, bool checkForOverflow = false, Sign sign = Sign.None) DecodedInstruction BinaryNumeric(BinaryNumericOperator @operator, bool checkForOverflow = false, Sign sign = Sign.None)
{ {
var right = Pop(); var right = Pop();
var left = Pop(); var left = Pop();

Loading…
Cancel
Save