Browse Source

ILAst transform for named arguments.

pull/1167/head
Daniel Grunwald 7 years ago
parent
commit
bcc1586f72
  1. 2
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  2. 16
      ICSharpCode.Decompiler/DecompilerSettings.cs
  3. 2
      ICSharpCode.Decompiler/IL/InstructionFlags.cs
  4. 34
      ICSharpCode.Decompiler/IL/Instructions/Block.cs
  5. 7
      ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
  6. 150
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

2
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -717,6 +717,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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) {

16
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -60,6 +60,7 @@ namespace ICSharpCode.Decompiler @@ -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 @@ -635,6 +636,21 @@ namespace ICSharpCode.Decompiler
}
}
bool namedArguments = false;
/// <summary>
/// Gets/Sets whether named arguments should be used.
/// </summary>
public bool NamedArguments {
get { return namedArguments; }
set {
if (namedArguments != value) {
namedArguments = value;
OnPropertyChanged();
}
}
}
#region Options to aid VB decompilation
bool assumeArrayLengthFitsIntoInt32 = true;

2
ICSharpCode.Decompiler/IL/InstructionFlags.cs

@ -70,7 +70,7 @@ namespace ICSharpCode.Decompiler.IL @@ -70,7 +70,7 @@ namespace ICSharpCode.Decompiler.IL
/// The instruction contains some kind of internal control flow.
/// </summary>
/// <remarks>
/// 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

34
ICSharpCode.Decompiler/IL/Instructions/Block.cs

@ -114,6 +114,21 @@ namespace ICSharpCode.Decompiler.IL @@ -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 @@ -311,6 +326,23 @@ namespace ICSharpCode.Decompiler.IL
/// final: ldloc s
/// }
/// </example>
CallInlineAssign
CallInlineAssign,
/// <summary>
/// Call using named arguments.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <example>
/// Block {
/// stloc arg0 = ...
/// stloc arg1 = ...
/// final: call M(..., arg1, arg0, ...)
/// }
/// </example>
CallWithNamedArgs,
}
}

7
ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs

@ -65,6 +65,13 @@ namespace ICSharpCode.Decompiler.IL @@ -65,6 +65,13 @@ namespace ICSharpCode.Decompiler.IL
this.Arguments = new InstructionCollection<ILInstruction>(this, 0);
}
/// <summary>
/// Gets whether this is an instance call (i.e. whether the first argument is the 'this' pointer).
/// </summary>
public bool IsInstanceCall {
get { return !(Method.IsStatic || OpCode == OpCode.NewObj); }
}
/// <summary>
/// Gets the parameter for the argument with the specified index.
/// Returns null for the <c>this</c> parameter.

150
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -190,8 +190,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -190,8 +190,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// </summary>
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 @@ -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<ILFunction>().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 @@ -351,59 +375,149 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// </summary>
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
{
/// <summary>
/// Found a load; inlining is possible.
/// </summary>
Found,
/// <summary>
/// Load not found and re-ordering not possible. Stop the search.
/// </summary>
Stop,
/// <summary>
/// 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.
/// </summary>
Continue,
/// <summary>
/// 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.
/// </summary>
NamedArgument,
}
/// <summary>
/// Finds the position to inline to.
/// </summary>
/// <returns>true = found; false = cannot continue search; null = not found</returns>
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;
}
/// <summary>

Loading…
Cancel
Save