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 @@
@@ -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 @@
@@ -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