diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
index 1792b1787..f17ace897 100644
--- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
@@ -717,6 +717,8 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override Statement VisitBlock(Block block)
{
+ if (block.Kind != BlockKind.ControlFlow)
+ return Default(block);
// Block without container
BlockStatement blockStatement = new BlockStatement();
foreach (var inst in block.Instructions) {
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index 416e8aa7e..570fdf096 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -60,6 +60,7 @@ namespace ICSharpCode.Decompiler
}
if (languageVersion < CSharp.LanguageVersion.CSharp4) {
dynamic = false;
+ namedArguments = false;
// * named and optional arguments (not supported yet)
}
if (languageVersion < CSharp.LanguageVersion.CSharp5) {
@@ -635,6 +636,21 @@ namespace ICSharpCode.Decompiler
}
}
+ bool namedArguments = false;
+
+ ///
+ /// Gets/Sets whether named arguments should be used.
+ ///
+ public bool NamedArguments {
+ get { return namedArguments; }
+ set {
+ if (namedArguments != value) {
+ namedArguments = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
#region Options to aid VB decompilation
bool assumeArrayLengthFitsIntoInt32 = true;
diff --git a/ICSharpCode.Decompiler/IL/InstructionFlags.cs b/ICSharpCode.Decompiler/IL/InstructionFlags.cs
index 1fcd6ee6d..bf0ddd517 100644
--- a/ICSharpCode.Decompiler/IL/InstructionFlags.cs
+++ b/ICSharpCode.Decompiler/IL/InstructionFlags.cs
@@ -70,7 +70,7 @@ namespace ICSharpCode.Decompiler.IL
/// The instruction contains some kind of internal control flow.
///
///
- /// If this flag is not set, the all descendants of the instruction are fully evaluated (modulo MayThrow/MayBranch)
+ /// If this flag is not set, all descendants of the instruction are fully evaluated (modulo MayThrow/MayBranch)
/// in left-to-right pre-order.
///
/// Note that branch instructions don't have this flag set, because their control flow is not internal
diff --git a/ICSharpCode.Decompiler/IL/Instructions/Block.cs b/ICSharpCode.Decompiler/IL/Instructions/Block.cs
index c3d5838fc..dc08bbb55 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/Block.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/Block.cs
@@ -114,6 +114,21 @@ namespace ICSharpCode.Decompiler.IL
case BlockKind.CallInlineAssign:
Debug.Assert(MatchInlineAssignBlock(out _, out _));
break;
+ case BlockKind.CallWithNamedArgs:
+ Debug.Assert(finalInstruction is CallInstruction);
+ foreach (var inst in Instructions) {
+ var stloc = inst as StLoc;
+ Debug.Assert(stloc != null, "Instructions in CallWithNamedArgs must be assignments");
+ Debug.Assert(stloc.Variable.IsSingleDefinition && stloc.Variable.LoadCount == 1);
+ Debug.Assert(stloc.Variable.LoadInstructions.Single().Parent == finalInstruction);
+ }
+ var call = (CallInstruction)finalInstruction;
+ if (call.IsInstanceCall) {
+ // special case: with instance calls, Instructions[0] must be for the this parameter
+ ILVariable v = ((StLoc)Instructions[0]).Variable;
+ Debug.Assert(call.Arguments[0].MatchLdLoc(v));
+ }
+ break;
}
}
@@ -311,6 +326,23 @@ namespace ICSharpCode.Decompiler.IL
/// final: ldloc s
/// }
///
- CallInlineAssign
+ CallInlineAssign,
+ ///
+ /// Call using named arguments.
+ ///
+ ///
+ /// Each instruction is assigning to a new local.
+ /// The final instruction is a call.
+ /// The locals for this block have exactly one store and
+ /// exactly one load, which must be an immediate argument to the call.
+ ///
+ ///
+ /// Block {
+ /// stloc arg0 = ...
+ /// stloc arg1 = ...
+ /// final: call M(..., arg1, arg0, ...)
+ /// }
+ ///
+ CallWithNamedArgs,
}
}
diff --git a/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
index 8eff8c227..81344b8ce 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
@@ -65,6 +65,13 @@ namespace ICSharpCode.Decompiler.IL
this.Arguments = new InstructionCollection(this, 0);
}
+ ///
+ /// Gets whether this is an instance call (i.e. whether the first argument is the 'this' pointer).
+ ///
+ public bool IsInstanceCall {
+ get { return !(Method.IsStatic || OpCode == OpCode.NewObj); }
+ }
+
///
/// Gets the parameter for the argument with the specified index.
/// Returns null for the this parameter.
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
index 6fa0e1283..9ab415d4e 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
@@ -190,8 +190,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
///
static bool DoInline(ILVariable v, ILInstruction inlinedExpression, ILInstruction next, bool aggressive, ILTransformContext context)
{
- ILInstruction loadInst;
- if (FindLoadInNext(next, v, inlinedExpression, out loadInst) == true) {
+ var r = FindLoadInNext(next, v, inlinedExpression, out var loadInst);
+ if (r == FindResult.Found) {
if (loadInst.OpCode == OpCode.LdLoca) {
if (!IsGeneratedValueTypeTemporary(next, (LdLoca)loadInst, v, inlinedExpression))
return false;
@@ -213,6 +213,30 @@ namespace ICSharpCode.Decompiler.IL.Transforms
loadInst.ReplaceWith(inlinedExpression);
}
return true;
+ } else if (r == FindResult.NamedArgument && context.Settings.NamedArguments) {
+ Debug.Assert(loadInst.OpCode == OpCode.LdLoc);
+ //if (!aggressive && v.Kind != VariableKind.StackSlot && !NonAggressiveInlineInto(next, loadInst, inlinedExpression, v))
+ // return false;
+ context.Step($"Introduce named argument '{v.Name}'", inlinedExpression);
+ var call = (CallInstruction)loadInst.Parent;
+ if (!(call.Parent is Block namedArgBlock) || namedArgBlock.Kind != BlockKind.CallWithNamedArgs) {
+ // create namedArgBlock:
+ namedArgBlock = new Block(BlockKind.CallWithNamedArgs);
+ call.ReplaceWith(namedArgBlock);
+ namedArgBlock.FinalInstruction = call;
+ if (call.IsInstanceCall) {
+ IType thisVarType = call.Method.DeclaringType;
+ if (CallInstruction.ExpectedTypeForThisPointer(thisVarType) == StackType.Ref) {
+ thisVarType = new ByReferenceType(thisVarType);
+ }
+ var function = call.Ancestors.OfType().First();
+ var thisArgVar = function.RegisterVariable(VariableKind.StackSlot, thisVarType, "this_arg");
+ namedArgBlock.Instructions.Add(new StLoc(thisArgVar, call.Arguments[0]));
+ call.Arguments[0] = new LdLoc(thisArgVar);
+ }
+ }
+ namedArgBlock.Instructions.Insert(call.IsInstanceCall ? 1 : 0, new StLoc(v, inlinedExpression));
+ return true;
}
return false;
}
@@ -351,59 +375,149 @@ namespace ICSharpCode.Decompiler.IL.Transforms
///
public static bool CanInlineInto(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved)
{
- ILInstruction loadInst;
- return FindLoadInNext(expr, v, expressionBeingMoved, out loadInst) == true;
+ return FindLoadInNext(expr, v, expressionBeingMoved, out _) == FindResult.Found;
+ }
+
+ enum FindResult
+ {
+ ///
+ /// Found a load; inlining is possible.
+ ///
+ Found,
+ ///
+ /// Load not found and re-ordering not possible. Stop the search.
+ ///
+ Stop,
+ ///
+ /// Load not found, but the expressionBeingMoved can be re-ordered with regards to the
+ /// tested expression, so we may continue searching for the matching load.
+ ///
+ Continue,
+ ///
+ /// Found a load in call, but re-ordering not possible with regards to the
+ /// other call arguments.
+ /// Inlining is not possible, but we might convert the call to named arguments.
+ ///
+ NamedArgument,
}
///
/// Finds the position to inline to.
///
/// true = found; false = cannot continue search; null = not found
- static bool? FindLoadInNext(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved, out ILInstruction loadInst)
+ static FindResult FindLoadInNext(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved, out ILInstruction loadInst)
{
loadInst = null;
if (expr == null)
- return false;
+ return FindResult.Stop;
if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) {
// Match found, we can inline
loadInst = expr;
- return true;
- } else if (expr is Block block && block.Instructions.Count > 0) {
- // Inlining into inline-blocks? only for some block types, and only into the first instruction.
+ return FindResult.Found;
+ } else if (expr is Block block) {
+ // Inlining into inline-blocks?
switch (block.Kind) {
case BlockKind.ArrayInitializer:
case BlockKind.CollectionInitializer:
case BlockKind.ObjectInitializer:
- return FindLoadInNext(block.Instructions[0], v, expressionBeingMoved, out loadInst) ?? false;
+ // Allow inlining into the first instruction of the block
+ if (block.Instructions.Count == 0)
+ return FindResult.Stop;
+ return NoContinue(FindLoadInNext(block.Instructions[0], v, expressionBeingMoved, out loadInst));
// If FindLoadInNext() returns null, we still can't continue searching
// because we can't inline over the remainder of the block.
+ case BlockKind.CallWithNamedArgs:
+ return CanExtendNamedArgument(block, v, expressionBeingMoved, out loadInst);
default:
- return false;
+ return FindResult.Stop;
}
} else if (expr is BlockContainer container && container.EntryPoint.IncomingEdgeCount == 1) {
// Possibly a switch-container, allow inlining into the switch instruction:
- return FindLoadInNext(container.EntryPoint.Instructions[0], v, expressionBeingMoved, out loadInst) ?? false;
+ return NoContinue(FindLoadInNext(container.EntryPoint.Instructions[0], v, expressionBeingMoved, out loadInst));
// If FindLoadInNext() returns null, we still can't continue searching
// because we can't inline over the remainder of the blockcontainer.
} else if (expr is NullableRewrap) {
// Inlining into nullable.rewrap is OK unless the expression being inlined
// contains a nullable.wrap that isn't being re-wrapped within the expression being inlined.
if (expressionBeingMoved.HasFlag(InstructionFlags.MayUnwrapNull))
- return false;
+ return FindResult.Stop;
}
foreach (var child in expr.Children) {
if (!child.SlotInfo.CanInlineInto)
- return false;
+ return FindResult.Stop;
// Recursively try to find the load instruction
- bool? r = FindLoadInNext(child, v, expressionBeingMoved, out loadInst);
- if (r != null)
+ FindResult r = FindLoadInNext(child, v, expressionBeingMoved, out loadInst);
+ if (r != FindResult.Continue) {
+ if (r == FindResult.Stop && expr is CallInstruction call)
+ return CanIntroduceNamedArgument(call, child, v, out loadInst);
return r;
+ }
}
if (IsSafeForInlineOver(expr, expressionBeingMoved))
- return null; // continue searching
+ return FindResult.Continue; // continue searching
+ else
+ return FindResult.Stop; // abort, inlining not possible
+ }
+
+ private static FindResult NoContinue(FindResult findResult)
+ {
+ if (findResult == FindResult.Continue)
+ return FindResult.Stop;
else
- return false; // abort, inlining not possible
+ return findResult;
+ }
+
+ private static FindResult CanIntroduceNamedArgument(CallInstruction call, ILInstruction child, ILVariable v, out ILInstruction loadInst)
+ {
+ loadInst = null;
+ Debug.Assert(child.Parent == call);
+ if (call.IsInstanceCall && child.ChildIndex == 0)
+ return FindResult.Stop; // cannot use named arg to move expressionBeingMoved before this pointer
+ if (call.Method.IsOperator || call.Method.IsAccessor)
+ return FindResult.Stop; // cannot use named arg for operators or accessors
+ for (int i = child.ChildIndex; i < call.Arguments.Count; i++) {
+ if (call.Arguments[i] is LdLoc ldloc && ldloc.Variable == v) {
+ loadInst = ldloc;
+ return FindResult.NamedArgument;
+ }
+ }
+ return FindResult.Stop;
+ }
+
+ private static FindResult CanExtendNamedArgument(Block block, ILVariable v, ILInstruction expressionBeingMoved, out ILInstruction loadInst)
+ {
+ Debug.Assert(block.Kind == BlockKind.CallWithNamedArgs);
+ var firstArg = ((StLoc)block.Instructions[0]).Value;
+ var r = FindLoadInNext(firstArg, v, expressionBeingMoved, out loadInst);
+ if (r == FindResult.Found || r == FindResult.NamedArgument) {
+ return r; // OK, inline into first instruction of block
+ }
+ var call = (CallInstruction)block.FinalInstruction;
+ if (call.IsInstanceCall) {
+ // For instance calls, block.Instructions[0] is the argument
+ // for the 'this' pointer. We can only insert at position 1.
+ if (r == FindResult.Stop) {
+ // error: can't move expressionBeingMoved after block.Instructions[0]
+ return FindResult.Stop;
+ }
+ // Because we always ensure block.Instructions[0] is the 'this' argument,
+ // it's possible that the place we actually need to inline into
+ // is within block.Instructions[1]:
+ if (block.Instructions.Count > 1) {
+ r = FindLoadInNext(block.Instructions[1], v, expressionBeingMoved, out loadInst);
+ if (r == FindResult.Found || r == FindResult.NamedArgument) {
+ return r; // OK, inline into block.Instructions[1]
+ }
+ }
+ }
+ foreach (var arg in call.Arguments) {
+ if (arg.MatchLdLoc(v)) {
+ loadInst = arg;
+ return FindResult.NamedArgument;
+ }
+ }
+ return FindResult.Stop;
}
///