You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

126 lines
4.9 KiB

  1. using System.Diagnostics;
  2. using System.Linq;
  3. using ICSharpCode.Decompiler.TypeSystem;
  4. namespace ICSharpCode.Decompiler.IL.Transforms
  5. {
  6. using FindResult = ILInlining.FindResult;
  7. using FindResultType = ILInlining.FindResultType;
  8. public class NamedArgumentTransform : IStatementTransform
  9. {
  10. internal static FindResult CanIntroduceNamedArgument(CallInstruction call, ILInstruction child, ILVariable v, ILInstruction expressionBeingMoved)
  11. {
  12. Debug.Assert(child.Parent == call);
  13. if (call.IsInstanceCall && child.ChildIndex == 0)
  14. return FindResult.Stop; // cannot use named arg to move expressionBeingMoved before this pointer
  15. if (call.Method.IsOperator || call.Method.IsAccessor)
  16. return FindResult.Stop; // cannot use named arg for operators or accessors
  17. if (call.Method is VarArgInstanceMethod)
  18. return FindResult.Stop; // CallBuilder doesn't support named args when using varargs
  19. if (call.Method.IsConstructor)
  20. {
  21. IType type = call.Method.DeclaringType;
  22. if (type.Kind == TypeKind.Delegate || type.IsAnonymousType())
  23. return FindResult.Stop;
  24. }
  25. if (call.Method.Parameters.Any(p => string.IsNullOrEmpty(p.Name)))
  26. return FindResult.Stop; // cannot use named arguments
  27. for (int i = child.ChildIndex; i < call.Arguments.Count; i++)
  28. {
  29. var r = ILInlining.FindLoadInNext(call.Arguments[i], v, expressionBeingMoved, InliningOptions.None);
  30. if (r.Type == FindResultType.Found)
  31. {
  32. return FindResult.NamedArgument(r.LoadInst, call.Arguments[i]);
  33. }
  34. }
  35. return FindResult.Stop;
  36. }
  37. internal static FindResult CanExtendNamedArgument(Block block, ILVariable v, ILInstruction expressionBeingMoved)
  38. {
  39. Debug.Assert(block.Kind == BlockKind.CallWithNamedArgs);
  40. var firstArg = ((StLoc)block.Instructions[0]).Value;
  41. var r = ILInlining.FindLoadInNext(firstArg, v, expressionBeingMoved, InliningOptions.IntroduceNamedArguments);
  42. if (r.Type == FindResultType.Found || r.Type == FindResultType.NamedArgument)
  43. {
  44. return r; // OK, inline into first instruction of block
  45. }
  46. var call = (CallInstruction)block.FinalInstruction;
  47. if (call.IsInstanceCall)
  48. {
  49. // For instance calls, block.Instructions[0] is the argument
  50. // for the 'this' pointer. We can only insert at position 1.
  51. if (r.Type == FindResultType.Stop)
  52. {
  53. // error: can't move expressionBeingMoved after block.Instructions[0]
  54. return FindResult.Stop;
  55. }
  56. // Because we always ensure block.Instructions[0] is the 'this' argument,
  57. // it's possible that the place we actually need to inline into
  58. // is within block.Instructions[1]:
  59. if (block.Instructions.Count > 1)
  60. {
  61. r = ILInlining.FindLoadInNext(block.Instructions[1], v, expressionBeingMoved, InliningOptions.IntroduceNamedArguments);
  62. if (r.Type == FindResultType.Found || r.Type == FindResultType.NamedArgument)
  63. {
  64. return r; // OK, inline into block.Instructions[1]
  65. }
  66. }
  67. }
  68. foreach (var arg in call.Arguments)
  69. {
  70. if (arg.MatchLdLoc(v))
  71. {
  72. return FindResult.NamedArgument(arg, arg);
  73. }
  74. }
  75. return FindResult.Stop;
  76. }
  77. /// <summary>
  78. /// Introduce a named argument for 'arg' and evaluate it before the other arguments
  79. /// (except for the "this" pointer)
  80. /// </summary>
  81. internal static void IntroduceNamedArgument(ILInstruction arg, ILTransformContext context)
  82. {
  83. var call = (CallInstruction)arg.Parent;
  84. Debug.Assert(context.Function == call.Ancestors.OfType<ILFunction>().First());
  85. var type = context.TypeSystem.FindType(arg.ResultType);
  86. var v = context.Function.RegisterVariable(VariableKind.NamedArgument, type);
  87. context.Step($"Introduce named argument '{v.Name}'", arg);
  88. if (!(call.Parent is Block namedArgBlock) || namedArgBlock.Kind != BlockKind.CallWithNamedArgs)
  89. {
  90. // create namedArgBlock:
  91. namedArgBlock = new Block(BlockKind.CallWithNamedArgs);
  92. call.ReplaceWith(namedArgBlock);
  93. namedArgBlock.FinalInstruction = call;
  94. if (call.IsInstanceCall)
  95. {
  96. IType thisVarType = call.ConstrainedTo ?? call.Method.DeclaringType;
  97. if (CallInstruction.ExpectedTypeForThisPointer(thisVarType) == StackType.Ref)
  98. {
  99. thisVarType = new ByReferenceType(thisVarType);
  100. }
  101. var thisArgVar = context.Function.RegisterVariable(VariableKind.NamedArgument, thisVarType, "this_arg");
  102. namedArgBlock.Instructions.Add(new StLoc(thisArgVar, call.Arguments[0]));
  103. call.Arguments[0] = new LdLoc(thisArgVar);
  104. }
  105. }
  106. int argIndex = arg.ChildIndex;
  107. Debug.Assert(call.Arguments[argIndex] == arg);
  108. namedArgBlock.Instructions.Insert(call.IsInstanceCall ? 1 : 0, new StLoc(v, arg));
  109. call.Arguments[argIndex] = new LdLoc(v);
  110. }
  111. public void Run(Block block, int pos, StatementTransformContext context)
  112. {
  113. if (!context.Settings.NamedArguments)
  114. return;
  115. var options = ILInlining.OptionsForBlock(block, pos, context);
  116. options |= InliningOptions.IntroduceNamedArguments;
  117. ILInlining.InlineOneIfPossible(block, pos, options, context: context);
  118. }
  119. }
  120. }