mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
For user-defined increments, there were problems with Roslyn was optimizing out one of the stores. The new transform FixRemainingIncrements now takes increments/decrements that were not detected by TransformAssignment and introduces a temporary variable that can be incremented. This sometimes requires un-inlining via the new ILInstruction.Extract() operation. Extract() is not supported in all possible contexts, so it is possible but unlikely that some op_Increment calls remain. For decimals, the situation is different: legacy csc actually was optimizing "d + 1m" to "op_Increment(d)", so we can get rid of any left-over increments by undoing this optimization. This now happens in ReplaceMethodCallsWithOperators.pull/1612/head
11 changed files with 336 additions and 1 deletions
@ -0,0 +1,65 @@ |
|||||||
|
using System; |
||||||
|
using System.Linq; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Text; |
||||||
|
using System.Diagnostics; |
||||||
|
using ICSharpCode.Decompiler.TypeSystem; |
||||||
|
|
||||||
|
namespace ICSharpCode.Decompiler.IL.Transforms |
||||||
|
{ |
||||||
|
public class FixRemainingIncrements : IILTransform |
||||||
|
{ |
||||||
|
void IILTransform.Run(ILFunction function, ILTransformContext context) |
||||||
|
{ |
||||||
|
var callsToFix = new List<Call>(); |
||||||
|
foreach (var call in function.Descendants.OfType<Call>()) { |
||||||
|
if (!(call.Method.IsOperator && (call.Method.Name == "op_Increment" || call.Method.Name == "op_Decrement"))) |
||||||
|
continue; |
||||||
|
if (call.Arguments.Count != 1) |
||||||
|
continue; |
||||||
|
if (call.Method.DeclaringType.IsKnownType(KnownTypeCode.Decimal)) { |
||||||
|
// For decimal, legacy csc can optimize "d + 1m" to "op_Increment(d)".
|
||||||
|
// We can handle these calls in ReplaceMethodCallsWithOperators.
|
||||||
|
continue; |
||||||
|
} |
||||||
|
callsToFix.Add(call); |
||||||
|
} |
||||||
|
foreach (var call in callsToFix) { |
||||||
|
// A user-defined increment/decrement that was not handled by TransformAssignment.
|
||||||
|
// This can happen because the variable-being-incremented was optimized out by Roslyn,
|
||||||
|
// e.g.
|
||||||
|
// public void Issue1552Pre(UserType a, UserType b)
|
||||||
|
// {
|
||||||
|
// UserType num = a + b;
|
||||||
|
// Console.WriteLine(++num);
|
||||||
|
// }
|
||||||
|
// can end up being compiled to:
|
||||||
|
// Console.WriteLine(UserType.op_Increment(a + b));
|
||||||
|
if (call.SlotInfo == StLoc.ValueSlot && call.Parent.SlotInfo == Block.InstructionSlot) { |
||||||
|
var store = (StLoc)call.Parent; |
||||||
|
var block = (Block)store.Parent; |
||||||
|
context.Step($"Fix {call.Method.Name} call at 0x{call.StartILOffset:x4} using {store.Variable.Name}", call); |
||||||
|
// stloc V(call op_Increment(...))
|
||||||
|
// ->
|
||||||
|
// stloc V(...)
|
||||||
|
// compound.assign op_Increment(V)
|
||||||
|
call.ReplaceWith(call.Arguments[0]); |
||||||
|
block.Instructions.Insert(store.ChildIndex + 1, |
||||||
|
new UserDefinedCompoundAssign(call.Method, CompoundEvalMode.EvaluatesToNewValue, |
||||||
|
new LdLoca(store.Variable), CompoundTargetKind.Address, new LdcI4(1)).WithILRange(call)); |
||||||
|
} else { |
||||||
|
context.Step($"Fix {call.Method.Name} call at 0x{call.StartILOffset:x4} using new local", call); |
||||||
|
var newVariable = call.Arguments[0].Extract(); |
||||||
|
if (newVariable == null) { |
||||||
|
Debug.Fail("Failed to extract argument of remaining increment/decrement"); |
||||||
|
continue; |
||||||
|
} |
||||||
|
newVariable.Type = call.GetParameter(0).Type; |
||||||
|
Debug.Assert(call.Arguments[0].MatchLdLoc(newVariable)); |
||||||
|
call.ReplaceWith(new UserDefinedCompoundAssign(call.Method, CompoundEvalMode.EvaluatesToNewValue, |
||||||
|
new LdLoca(newVariable), CompoundTargetKind.Address, new LdcI4(1)).WithILRange(call)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,103 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Linq; |
||||||
|
using System.Threading; |
||||||
|
|
||||||
|
namespace ICSharpCode.Decompiler.IL.Transforms |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Context object for the ILInstruction.Extract() operation.
|
||||||
|
/// </summary>
|
||||||
|
class ExtractionContext |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Nearest function, used for registering the new locals that are created by extraction.
|
||||||
|
/// </summary>
|
||||||
|
readonly ILFunction Function; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Combined flags of all instructions being moved.
|
||||||
|
/// </summary>
|
||||||
|
internal InstructionFlags FlagsBeingMoved; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of actions to be executed when performing the extraction.
|
||||||
|
///
|
||||||
|
/// Each function in this list has the side-effect of replacing the instruction-to-be-moved
|
||||||
|
/// with a load of a fresh temporary variable; and returns the the store to the temporary variable,
|
||||||
|
/// which will be inserted at block-level.
|
||||||
|
/// </summary>
|
||||||
|
readonly List<Func<ILInstruction>> MoveActions = new List<Func<ILInstruction>>(); |
||||||
|
|
||||||
|
ExtractionContext(ILFunction function) |
||||||
|
{ |
||||||
|
Debug.Assert(function != null); |
||||||
|
this.Function = function; |
||||||
|
} |
||||||
|
|
||||||
|
internal void RegisterMove(ILInstruction predecessor) |
||||||
|
{ |
||||||
|
FlagsBeingMoved |= predecessor.Flags; |
||||||
|
MoveActions.Add(delegate { |
||||||
|
var v = Function.RegisterVariable(VariableKind.StackSlot, predecessor.ResultType); |
||||||
|
predecessor.ReplaceWith(new LdLoc(v)); |
||||||
|
return new StLoc(v, predecessor); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
internal void RegisterMoveIfNecessary(ILInstruction predecessor) |
||||||
|
{ |
||||||
|
if (!CanReorderWithInstructionsBeingMoved(predecessor)) { |
||||||
|
RegisterMove(predecessor); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Currently, <c>predecessor</c> is evaluated before the instructions being moved.
|
||||||
|
/// If this function returns true, <c>predecessor</c> can stay as-is, despite the move changing the evaluation order.
|
||||||
|
/// If this function returns false, <c>predecessor</c> will need to also move, to ensure the evaluation order stays unchanged.
|
||||||
|
/// </summary>
|
||||||
|
public bool CanReorderWithInstructionsBeingMoved(ILInstruction predecessor) |
||||||
|
{ |
||||||
|
// We could track the instructions being moved and be smarter about unnecessary moves,
|
||||||
|
// but given the limited scenarios where extraction is used so far,
|
||||||
|
// this seems unnecessary.
|
||||||
|
return predecessor.Flags == InstructionFlags.None; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts the specified instruction:
|
||||||
|
/// The instruction is replaced with a load of a new temporary variable;
|
||||||
|
/// and the instruction is moved to a store to said variable at block-level.
|
||||||
|
///
|
||||||
|
/// May return null if extraction is not possible.
|
||||||
|
/// </summary>
|
||||||
|
public static ILVariable Extract(ILInstruction instToExtract) |
||||||
|
{ |
||||||
|
var function = instToExtract.Ancestors.OfType<ILFunction>().First(); |
||||||
|
ExtractionContext ctx = new ExtractionContext(function); |
||||||
|
ctx.FlagsBeingMoved = instToExtract.Flags; |
||||||
|
ILInstruction inst = instToExtract; |
||||||
|
while (inst != null) { |
||||||
|
if (inst.Parent is Block block && block.Kind == BlockKind.ControlFlow) { |
||||||
|
// We've reached the target block, and extraction is possible all the way.
|
||||||
|
int insertIndex = inst.ChildIndex; |
||||||
|
// Move instToExtract itself:
|
||||||
|
var v = function.RegisterVariable(VariableKind.StackSlot, instToExtract.ResultType); |
||||||
|
instToExtract.ReplaceWith(new LdLoc(v)); |
||||||
|
block.Instructions.Insert(insertIndex, new StLoc(v, instToExtract)); |
||||||
|
// Apply the other move actions:
|
||||||
|
foreach (var moveAction in ctx.MoveActions) { |
||||||
|
block.Instructions.Insert(insertIndex, moveAction()); |
||||||
|
} |
||||||
|
return v; |
||||||
|
} |
||||||
|
if (!inst.Parent.PrepareExtract(inst.ChildIndex, ctx)) |
||||||
|
return null; |
||||||
|
inst = inst.Parent; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue