Browse Source

Improve persistence of IL offsets through various transforms.

pull/1296/head
Chicken-Bones 7 years ago
parent
commit
e9b766d708
  1. 10
      ICSharpCode.Decompiler/IL/BlockBuilder.cs
  2. 4
      ICSharpCode.Decompiler/IL/ControlFlow/AwaitInCatchTransform.cs
  3. 12
      ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs
  4. 9
      ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs
  5. 7
      ICSharpCode.Decompiler/IL/ControlFlow/DetectPinnedRegions.cs
  6. 4
      ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs
  7. 1
      ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs
  8. 4
      ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
  9. 1
      ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs
  10. 2
      ICSharpCode.Decompiler/IL/Transforms/SwitchOnNullableTransform.cs
  11. 11
      ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
  12. 2
      ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs

10
ICSharpCode.Decompiler/IL/BlockBuilder.cs

@ -75,6 +75,7 @@ namespace ICSharpCode.Decompiler.IL
var tryBlock = new BlockContainer(); var tryBlock = new BlockContainer();
tryBlock.ILRange = tryRange; tryBlock.ILRange = tryRange;
tryCatch = new TryCatch(tryBlock); tryCatch = new TryCatch(tryBlock);
tryCatch.ILRange = tryRange;
tryCatchList.Add(tryCatch); tryCatchList.Add(tryCatch);
tryInstructionList.Add(tryCatch); tryInstructionList.Add(tryCatch);
} }
@ -90,7 +91,11 @@ namespace ICSharpCode.Decompiler.IL
filter = new LdcI4(1); filter = new LdcI4(1);
} }
tryCatch.Handlers.Add(new TryCatchHandler(filter, handlerBlock, variableByExceptionHandler[eh])); var handler = new TryCatchHandler(filter, handlerBlock, variableByExceptionHandler[eh]);
handler.AddILRange(filter.ILRange);
handler.AddILRange(handlerBlock.ILRange);
tryCatch.Handlers.Add(handler);
tryCatch.AddILRange(handler.ILRange);
} }
if (tryInstructionList.Count > 0) { if (tryInstructionList.Count > 0) {
tryInstructionList = tryInstructionList.OrderBy(tc => tc.TryBlock.ILRange.Start).ThenByDescending(tc => tc.TryBlock.ILRange.End).ToList(); tryInstructionList = tryInstructionList.OrderBy(tc => tc.TryBlock.ILRange.Start).ThenByDescending(tc => tc.TryBlock.ILRange.End).ToList();
@ -128,6 +133,7 @@ namespace ICSharpCode.Decompiler.IL
// Leave nested containers if necessary // Leave nested containers if necessary
while (start >= currentContainer.ILRange.End) { while (start >= currentContainer.ILRange.End) {
currentContainer = containerStack.Pop(); currentContainer = containerStack.Pop();
currentBlock = currentContainer.Blocks.Last();
} }
// Enter a handler if necessary // Enter a handler if necessary
BlockContainer handlerContainer; BlockContainer handlerContainer;
@ -136,6 +142,7 @@ namespace ICSharpCode.Decompiler.IL
currentContainer = handlerContainer; currentContainer = handlerContainer;
currentBlock = handlerContainer.EntryPoint; currentBlock = handlerContainer.EntryPoint;
} else { } else {
FinalizeCurrentBlock(start, fallthrough: false);
// Create the new block // Create the new block
currentBlock = new Block(); currentBlock = new Block();
currentContainer.Blocks.Add(currentBlock); currentContainer.Blocks.Add(currentBlock);
@ -172,6 +179,7 @@ namespace ICSharpCode.Decompiler.IL
{ {
if (currentBlock == null) if (currentBlock == null)
return; return;
Debug.Assert(currentBlock.ILRange.IsEmpty);
currentBlock.ILRange = new Interval(currentBlock.ILRange.Start, currentILOffset); currentBlock.ILRange = new Interval(currentBlock.ILRange.Start, currentILOffset);
if (fallthrough) { if (fallthrough) {
if (currentBlock.Instructions.LastOrDefault() is SwitchInstruction switchInst && switchInst.Sections.Last().Body.MatchNop()) { if (currentBlock.Instructions.LastOrDefault() is SwitchInstruction switchInst && switchInst.Sections.Last().Body.MatchNop()) {

4
ICSharpCode.Decompiler/IL/ControlFlow/AwaitInCatchTransform.cs

@ -265,6 +265,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
var outer = BlockContainer.FindClosestContainer(container.Parent); var outer = BlockContainer.FindClosestContainer(container.Parent);
if (outer != null) changedContainers.Add(outer); if (outer != null) changedContainers.Add(outer);
finallyContainer.Blocks.Add(entryPointOfFinally); finallyContainer.Blocks.Add(entryPointOfFinally);
finallyContainer.ILRange = entryPointOfFinally.ILRange;
exitOfFinally.Instructions.RemoveRange(tempStore.ChildIndex, 3); exitOfFinally.Instructions.RemoveRange(tempStore.ChildIndex, 3);
exitOfFinally.Instructions.Add(new Leave(finallyContainer)); exitOfFinally.Instructions.Add(new Leave(finallyContainer));
foreach (var branchToFinally in container.Descendants.OfType<Branch>()) { foreach (var branchToFinally in container.Descendants.OfType<Branch>()) {
@ -274,8 +275,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
foreach (var newBlock in additionalBlocksInFinally) { foreach (var newBlock in additionalBlocksInFinally) {
newBlock.Remove(); newBlock.Remove();
finallyContainer.Blocks.Add(newBlock); finallyContainer.Blocks.Add(newBlock);
finallyContainer.AddILRange(newBlock.ILRange);
} }
tryCatch.ReplaceWith(new TryFinally(tryCatch.TryBlock, finallyContainer)); tryCatch.ReplaceWith(new TryFinally(tryCatch.TryBlock, finallyContainer) {ILRange = tryCatch.TryBlock.ILRange});
} }
// clean up all modified containers // clean up all modified containers

12
ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs

@ -425,7 +425,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// </summary> /// </summary>
private void OrderIfBlocks(IfInstruction ifInst) private void OrderIfBlocks(IfInstruction ifInst)
{ {
if (IsEmpty(ifInst.FalseInst) || ifInst.TrueInst.ILRange.Start <= ifInst.FalseInst.ILRange.Start) if (IsEmpty(ifInst.FalseInst) || GetILRange(ifInst.TrueInst).Start <= GetILRange(ifInst.FalseInst).Start)
return; return;
context.Step("Swap then-branch with else-branch to match IL order", ifInst); context.Step("Swap then-branch with else-branch to match IL order", ifInst);
@ -437,6 +437,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
ifInst.Condition = Comp.LogicNot(ifInst.Condition); ifInst.Condition = Comp.LogicNot(ifInst.Condition);
} }
public static Interval GetILRange(ILInstruction inst)
{
// some compilers merge the leave instructions for different arguments using stack variables
// these get split and inlined, but the ILRange of the value remains a better indicator of the actual location
if (inst is Leave leave && !leave.Value.MatchNop())
return leave.Value.ILRange;
return inst.ILRange;
}
/// <summary> /// <summary>
/// Compares the current block exit, and the exit of ifInst.ThenInst /// Compares the current block exit, and the exit of ifInst.ThenInst
/// and inverts if necessary to pick the better exit /// and inverts if necessary to pick the better exit

9
ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs

@ -132,7 +132,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} else if (targetBlock.Instructions.Count == 1 && targetBlock.Instructions[0] is Leave leave && leave.Value.MatchNop()) { } else if (targetBlock.Instructions.Count == 1 && targetBlock.Instructions[0] is Leave leave && leave.Value.MatchNop()) {
context.Step("Replace branch to leave with leave", branch); context.Step("Replace branch to leave with leave", branch);
// Replace branches to 'leave' instruction with the leave instruction // Replace branches to 'leave' instruction with the leave instruction
branch.ReplaceWith(leave.Clone()); var leave2 = leave.Clone();
if (!branch.ILRange.IsEmpty) // use the ILRange of the branch if possible
leave2.ILRange = branch.ILRange;
branch.ReplaceWith(leave2);
} }
if (targetBlock.IncomingEdgeCount == 0) if (targetBlock.IncomingEdgeCount == 0)
targetBlock.Instructions.Clear(); // mark the block for deletion targetBlock.Instructions.Clear(); // mark the block for deletion
@ -187,6 +190,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// The C# compiler generates a dead store for the condition of while (true) loops. // The C# compiler generates a dead store for the condition of while (true) loops.
block.Instructions.RemoveRange(block.Instructions.Count - 3, 2); block.Instructions.RemoveRange(block.Instructions.Count - 3, 2);
} }
if (block.ILRange.IsEmpty)
block.ILRange = targetBlock.ILRange;
block.Instructions.Remove(br); block.Instructions.Remove(br);
block.Instructions.AddRange(targetBlock.Instructions); block.Instructions.AddRange(targetBlock.Instructions);
targetBlock.Instructions.Clear(); // mark targetBlock for deletion targetBlock.Instructions.Clear(); // mark targetBlock for deletion

7
ICSharpCode.Decompiler/IL/ControlFlow/DetectPinnedRegions.cs

@ -147,7 +147,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (p.StackType != StackType.Ref) { if (p.StackType != StackType.Ref) {
arrayToPointer = new Conv(arrayToPointer, p.StackType.ToPrimitiveType(), false, Sign.None); arrayToPointer = new Conv(arrayToPointer, p.StackType.ToPrimitiveType(), false, Sign.None);
} }
block.Instructions[block.Instructions.Count - 2] = new StLoc(p, arrayToPointer); block.Instructions[block.Instructions.Count - 2] = new StLoc(p, arrayToPointer) {
ILRange = block.Instructions[block.Instructions.Count - 2].ILRange
};
((Branch)block.Instructions.Last()).TargetBlock = targetBlock; ((Branch)block.Instructions.Last()).TargetBlock = targetBlock;
modified = true; modified = true;
} }
@ -343,7 +345,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
} }
stLoc.ReplaceWith(new PinnedRegion(stLoc.Variable, stLoc.Value, body)); stLoc.ReplaceWith(new PinnedRegion(stLoc.Variable, stLoc.Value, body) { ILRange = stLoc.ILRange });
block.Instructions.RemoveAt(block.Instructions.Count - 1); // remove branch into body block.Instructions.RemoveAt(block.Instructions.Count - 1); // remove branch into body
ProcessPinnedRegion((PinnedRegion)block.Instructions.Last()); ProcessPinnedRegion((PinnedRegion)block.Instructions.Last());
return true; return true;
@ -400,6 +402,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
foreach (var block in body.Blocks) foreach (var block in body.Blocks)
CreatePinnedRegion(block); CreatePinnedRegion(block);
body.Blocks.RemoveAll(b => b.Instructions.Count == 0); // remove dummy blocks body.Blocks.RemoveAll(b => b.Instructions.Count == 0); // remove dummy blocks
body.ILRange = body.EntryPoint.ILRange;
} }
private void MoveArrayToPointerToPinnedRegionInit(PinnedRegion pinnedRegion) private void MoveArrayToPointerToPinnedRegionInit(PinnedRegion pinnedRegion)

4
ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs

@ -646,6 +646,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (exitTargetBlock != null) if (exitTargetBlock != null)
oldEntryPoint.Instructions.Add(new Branch(exitTargetBlock)); oldEntryPoint.Instructions.Add(new Branch(exitTargetBlock));
loopContainer.ILRange = newEntryPoint.ILRange;
MoveBlocksIntoContainer(loop, loopContainer); MoveBlocksIntoContainer(loop, loopContainer);
// Rewrite branches within the loop from oldEntryPoint to newEntryPoint: // Rewrite branches within the loop from oldEntryPoint to newEntryPoint:
@ -731,7 +732,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (exitTargetBlock != null) { if (exitTargetBlock != null) {
block.Instructions.Add(new Branch(exitTargetBlock)); block.Instructions.Add(new Branch(exitTargetBlock));
} }
switchContainer.ILRange = newEntryPoint.ILRange;
MoveBlocksIntoContainer(nodesInSwitch, switchContainer); MoveBlocksIntoContainer(nodesInSwitch, switchContainer);
// Rewrite branches within the loop from oldEntryPoint to newEntryPoint: // Rewrite branches within the loop from oldEntryPoint to newEntryPoint:

1
ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs

@ -180,6 +180,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Remove branch/leave after if; it's getting moved into a section. // Remove branch/leave after if; it's getting moved into a section.
block.Instructions.RemoveAt(block.Instructions.Count - 1); block.Instructions.RemoveAt(block.Instructions.Count - 1);
} }
sw.ILRange = block.Instructions[block.Instructions.Count - 1].ILRange;
block.Instructions[block.Instructions.Count - 1] = sw; block.Instructions[block.Instructions.Count - 1] = sw;
// mark all inner blocks that were converted to the switch statement for deletion // mark all inner blocks that were converted to the switch statement for deletion

4
ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs

@ -674,6 +674,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
// copy over the instruction to the new block // copy over the instruction to the new block
newBlock.Instructions.Add(oldInst); newBlock.Instructions.Add(oldInst);
newBlock.AddILRange(oldInst.ILRange);
UpdateBranchTargets(oldInst); UpdateBranchTargets(oldInst);
} }
} }
@ -931,6 +932,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
tryBlock.Instructions.AddRange(block.Instructions); tryBlock.Instructions.AddRange(block.Instructions);
var tryBlockContainer = new BlockContainer(); var tryBlockContainer = new BlockContainer();
tryBlockContainer.Blocks.Add(tryBlock); tryBlockContainer.Blocks.Add(tryBlock);
tryBlockContainer.ILRange = tryBlock.ILRange;
stateToContainer.Add(state, tryBlockContainer); stateToContainer.Add(state, tryBlockContainer);
ILInstruction finallyBlock; ILInstruction finallyBlock;
@ -944,7 +946,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
block.Instructions.Clear(); block.Instructions.Clear();
block.Instructions.Add(new TryFinally(tryBlockContainer, finallyBlock)); block.Instructions.Add(new TryFinally(tryBlockContainer, finallyBlock) { ILRange = tryBlockContainer.ILRange});
} }
IMethod FindFinallyMethod(int state) IMethod FindFinallyMethod(int state)

1
ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs

@ -181,6 +181,7 @@ namespace ICSharpCode.Decompiler.IL
base.CheckInvariant(phase); base.CheckInvariant(phase);
Debug.Assert(Blocks.Count > 0 && EntryPoint == Blocks[0]); Debug.Assert(Blocks.Count > 0 && EntryPoint == Blocks[0]);
Debug.Assert(!IsConnected || EntryPoint?.IncomingEdgeCount >= 1); Debug.Assert(!IsConnected || EntryPoint?.IncomingEdgeCount >= 1);
Debug.Assert(EntryPoint == null || Parent is ILFunction || !ILRange.IsEmpty);
Debug.Assert(Blocks.All(b => b.HasFlag(InstructionFlags.EndPointUnreachable))); Debug.Assert(Blocks.All(b => b.HasFlag(InstructionFlags.EndPointUnreachable)));
Debug.Assert(Blocks.All(b => b.Kind == BlockKind.ControlFlow)); // this also implies that the blocks don't use FinalInstruction Debug.Assert(Blocks.All(b => b.Kind == BlockKind.ControlFlow)); // this also implies that the blocks don't use FinalInstruction
Block bodyStartBlock; Block bodyStartBlock;

2
ICSharpCode.Decompiler/IL/Transforms/SwitchOnNullableTransform.cs

@ -42,6 +42,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
for (int i = block.Instructions.Count - 1; i >= 0; i--) { for (int i = block.Instructions.Count - 1; i >= 0; i--) {
SwitchInstruction newSwitch; SwitchInstruction newSwitch;
if (MatchSwitchOnNullable(block.Instructions, i, out newSwitch)) { if (MatchSwitchOnNullable(block.Instructions, i, out newSwitch)) {
newSwitch.ILRange = block.Instructions[i - 2].ILRange;
block.Instructions[i + 1].ReplaceWith(newSwitch); block.Instructions[i + 1].ReplaceWith(newSwitch);
block.Instructions.RemoveRange(i - 2, 3); block.Instructions.RemoveRange(i - 2, 3);
i -= 2; i -= 2;
@ -49,6 +50,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
continue; continue;
} }
if (MatchRoslynSwitchOnNullable(block.Instructions, i, out newSwitch)) { if (MatchRoslynSwitchOnNullable(block.Instructions, i, out newSwitch)) {
newSwitch.ILRange = block.Instructions[i - 1].ILRange;
block.Instructions[i - 1].ReplaceWith(newSwitch); block.Instructions[i - 1].ReplaceWith(newSwitch);
block.Instructions.RemoveRange(i, 2); block.Instructions.RemoveRange(i, 2);
i--; i--;

11
ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs

@ -219,14 +219,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var inst = new SwitchInstruction(stringToInt); var inst = new SwitchInstruction(stringToInt);
inst.Sections.AddRange(sections); inst.Sections.AddRange(sections);
if (extraLoad) { if (extraLoad) {
inst.ILRange = instructions[i - 2].ILRange;
instructions[i - 2].ReplaceWith(inst); instructions[i - 2].ReplaceWith(inst);
instructions.RemoveRange(i - 1, 3); instructions.RemoveRange(i - 1, 3);
i -= 2; i -= 2;
} else { } else {
if (keepAssignmentBefore) { if (keepAssignmentBefore) {
inst.ILRange = instructions[i].ILRange;
instructions[i].ReplaceWith(inst); instructions[i].ReplaceWith(inst);
instructions.RemoveAt(i + 1); instructions.RemoveAt(i + 1);
} else { } else {
inst.ILRange = instructions[i - 1].ILRange;
instructions[i - 1].ReplaceWith(inst); instructions[i - 1].ReplaceWith(inst);
instructions.RemoveRange(i, 2); instructions.RemoveRange(i, 2);
i--; i--;
@ -304,7 +307,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var stringToInt = new StringToInt(switchValue, values.SelectArray(item => item.Item1)); var stringToInt = new StringToInt(switchValue, values.SelectArray(item => item.Item1));
var inst = new SwitchInstruction(stringToInt); var inst = new SwitchInstruction(stringToInt);
inst.Sections.AddRange(sections); inst.Sections.AddRange(sections);
inst.ILRange = instructions[i - 1].ILRange;
instructions[i].ReplaceWith(inst); instructions[i].ReplaceWith(inst);
instructions.RemoveAt(i + 1); instructions.RemoveAt(i + 1);
instructions.RemoveAt(i - 1); instructions.RemoveAt(i - 1);
@ -493,10 +497,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
instructions[i + 1].ReplaceWith(inst); instructions[i + 1].ReplaceWith(inst);
if (keepAssignmentBefore) { if (keepAssignmentBefore) {
// delete if (comp(ldloc switchValueVar == ldnull)) // delete if (comp(ldloc switchValueVar == ldnull))
inst.ILRange = instructions[i].ILRange;
instructions.RemoveAt(i); instructions.RemoveAt(i);
i--; i--;
} else { } else {
// delete both the if and the assignment before // delete both the if and the assignment before
inst.ILRange = instructions[i - 1].ILRange;
instructions.RemoveRange(i - 1, 2); instructions.RemoveRange(i - 1, 2);
i -= 2; i -= 2;
} }
@ -711,6 +717,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var stringToInt = new StringToInt(switchValue, stringValues); var stringToInt = new StringToInt(switchValue, stringValues);
var inst = new SwitchInstruction(stringToInt); var inst = new SwitchInstruction(stringToInt);
inst.Sections.AddRange(sections); inst.Sections.AddRange(sections);
inst.ILRange = block.Instructions[i].ILRange;
block.Instructions[i].ReplaceWith(inst); block.Instructions[i].ReplaceWith(inst);
block.Instructions.RemoveRange(i + 1, 3); block.Instructions.RemoveRange(i + 1, 3);
info.Transformed = true; info.Transformed = true;
@ -808,9 +815,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = defaultSection.Body }); newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = defaultSection.Body });
instructions[i].ReplaceWith(newSwitch); instructions[i].ReplaceWith(newSwitch);
if (keepAssignmentBefore) { if (keepAssignmentBefore) {
newSwitch.ILRange = instructions[i - 1].ILRange;
instructions.RemoveAt(i - 1); instructions.RemoveAt(i - 1);
i--; i--;
} else { } else {
newSwitch.ILRange = instructions[i - 2].ILRange;
instructions.RemoveRange(i - 2, 2); instructions.RemoveRange(i - 2, 2);
i -= 2; i -= 2;
} }

2
ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs

@ -152,11 +152,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!ReadParameters(instruction.Arguments[1], parameterList, parameterVariablesList, new SimpleTypeResolveContext(context.Function.Method))) if (!ReadParameters(instruction.Arguments[1], parameterList, parameterVariablesList, new SimpleTypeResolveContext(context.Function.Method)))
return (null, SpecialType.UnknownType); return (null, SpecialType.UnknownType);
var container = new BlockContainer(); var container = new BlockContainer();
container.ILRange = instruction.ILRange;
var functionType = instruction.Method.ReturnType.TypeArguments[0]; var functionType = instruction.Method.ReturnType.TypeArguments[0];
var returnType = functionType.GetDelegateInvokeMethod()?.ReturnType; var returnType = functionType.GetDelegateInvokeMethod()?.ReturnType;
var function = new ILFunction(returnType, parameterList, context.Function.GenericContext, container); var function = new ILFunction(returnType, parameterList, context.Function.GenericContext, container);
function.DelegateType = functionType; function.DelegateType = functionType;
function.Variables.AddRange(parameterVariablesList); function.Variables.AddRange(parameterVariablesList);
function.ILRange = instruction.ILRange;
lambdaStack.Push(function); lambdaStack.Push(function);
var (bodyInstruction, type) = ConvertInstruction(instruction.Arguments[0]); var (bodyInstruction, type) = ConvertInstruction(instruction.Arguments[0]);
lambdaStack.Pop(); lambdaStack.Pop();

Loading…
Cancel
Save