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.

300 lines
10 KiB

  1. // Copyright (c) 2018 Daniel Grunwald
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Diagnostics;
  21. using System.Linq;
  22. using System.Text;
  23. using ICSharpCode.Decompiler.TypeSystem;
  24. using ICSharpCode.Decompiler.Util;
  25. namespace ICSharpCode.Decompiler.IL.Transforms
  26. {
  27. public class UserDefinedLogicTransform : IStatementTransform
  28. {
  29. void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
  30. {
  31. if (LegacyPattern(block, pos, context))
  32. return;
  33. if (RoslynOptimized(block, pos, context))
  34. return;
  35. }
  36. bool RoslynOptimized(Block block, int pos, StatementTransformContext context)
  37. {
  38. // Roslyn, optimized pattern in combination with return statement:
  39. // if (logic.not(call op_False(ldloc lhsVar))) leave IL_0000 (call op_BitwiseAnd(ldloc lhsVar, rhsInst))
  40. // leave IL_0000(ldloc lhsVar)
  41. // ->
  42. // user.logic op_BitwiseAnd(ldloc lhsVar, rhsInst)
  43. if (!block.Instructions[pos].MatchIfInstructionPositiveCondition(out var condition, out var trueInst, out var falseInst))
  44. return false;
  45. if (trueInst.OpCode == OpCode.Nop)
  46. {
  47. trueInst = block.Instructions[pos + 1];
  48. }
  49. else if (falseInst.OpCode == OpCode.Nop)
  50. {
  51. falseInst = block.Instructions[pos + 1];
  52. }
  53. else
  54. {
  55. return false;
  56. }
  57. if (trueInst.MatchReturn(out var trueValue) && falseInst.MatchReturn(out var falseValue))
  58. {
  59. var transformed = Transform(condition, trueValue, falseValue);
  60. if (transformed == null)
  61. {
  62. transformed = TransformDynamic(condition, trueValue, falseValue);
  63. }
  64. if (transformed != null)
  65. {
  66. context.Step("User-defined short-circuiting logic operator (optimized return)", condition);
  67. ((Leave)block.Instructions[pos + 1]).Value = transformed;
  68. block.Instructions.RemoveAt(pos);
  69. return true;
  70. }
  71. }
  72. return false;
  73. }
  74. bool LegacyPattern(Block block, int pos, StatementTransformContext context)
  75. {
  76. // Legacy csc pattern:
  77. // stloc s(lhsInst)
  78. // if (logic.not(call op_False(ldloc s))) Block {
  79. // stloc s(call op_BitwiseAnd(ldloc s, rhsInst))
  80. // }
  81. // ->
  82. // stloc s(user.logic op_BitwiseAnd(lhsInst, rhsInst))
  83. if (!block.Instructions[pos].MatchStLoc(out var s, out var lhsInst))
  84. return false;
  85. if (!(s.Kind == VariableKind.StackSlot))
  86. return false;
  87. if (!(block.Instructions[pos + 1] is IfInstruction ifInst))
  88. return false;
  89. if (!ifInst.Condition.MatchLogicNot(out var condition))
  90. return false;
  91. if (!(MatchCondition(condition, out var s2, out string conditionMethodName) && s2 == s))
  92. return false;
  93. if (ifInst.FalseInst.OpCode != OpCode.Nop)
  94. return false;
  95. var trueInst = Block.Unwrap(ifInst.TrueInst);
  96. if (!trueInst.MatchStLoc(s, out var storeValue))
  97. return false;
  98. if (storeValue is Call call)
  99. {
  100. if (!MatchBitwiseCall(call, s, conditionMethodName))
  101. return false;
  102. if (s.IsUsedWithin(call.Arguments[1]))
  103. return false;
  104. context.Step("User-defined short-circuiting logic operator (legacy pattern)", condition);
  105. ((StLoc)block.Instructions[pos]).Value = new UserDefinedLogicOperator(call.Method, lhsInst, call.Arguments[1])
  106. .WithILRange(call);
  107. block.Instructions.RemoveAt(pos + 1);
  108. context.RequestRerun(); // the 'stloc s' may now be eligible for inlining
  109. return true;
  110. }
  111. return false;
  112. }
  113. static bool MatchCondition(ILInstruction condition, out ILVariable v, out string name)
  114. {
  115. v = null;
  116. name = null;
  117. if (!(condition is Call call && call.Method.IsOperator && call.Arguments.Count == 1 && !call.IsLifted))
  118. return false;
  119. name = call.Method.Name;
  120. if (!(name == "op_True" || name == "op_False"))
  121. return false;
  122. return call.Arguments[0].MatchLdLoc(out v);
  123. }
  124. static bool MatchBitwiseCall(Call call, ILVariable v, string conditionMethodName)
  125. {
  126. if (!(call != null && call.Method.IsOperator && call.Arguments.Count == 2 && !call.IsLifted))
  127. return false;
  128. if (!call.Arguments[0].MatchLdLoc(v))
  129. return false;
  130. return conditionMethodName == "op_False" && call.Method.Name == "op_BitwiseAnd"
  131. || conditionMethodName == "op_True" && call.Method.Name == "op_BitwiseOr";
  132. }
  133. /// <summary>
  134. /// if (call op_False(ldloc lhsVar)) ldloc lhsVar else call op_BitwiseAnd(ldloc lhsVar, rhsInst)
  135. /// -> user.logic op_BitwiseAnd(ldloc lhsVar, rhsInst)
  136. /// or
  137. /// if (call op_True(ldloc lhsVar)) ldloc lhsVar else call op_BitwiseOr(ldloc lhsVar, rhsInst)
  138. /// -> user.logic op_BitwiseOr(ldloc lhsVar, rhsInst)
  139. /// </summary>
  140. public static ILInstruction Transform(ILInstruction condition, ILInstruction trueInst, ILInstruction falseInst)
  141. {
  142. if (!MatchCondition(condition, out var lhsVar, out var conditionMethodName))
  143. return null;
  144. if (!trueInst.MatchLdLoc(lhsVar))
  145. return null;
  146. var call = falseInst as Call;
  147. if (!MatchBitwiseCall(call, lhsVar, conditionMethodName))
  148. return null;
  149. var result = new UserDefinedLogicOperator(call.Method, call.Arguments[0], call.Arguments[1]);
  150. result.AddILRange(condition);
  151. result.AddILRange(trueInst);
  152. result.AddILRange(call);
  153. return result;
  154. }
  155. public static ILInstruction TransformDynamic(ILInstruction condition, ILInstruction trueInst, ILInstruction falseInst)
  156. {
  157. // Check condition:
  158. System.Linq.Expressions.ExpressionType unaryOp;
  159. if (condition.MatchLdLoc(out var lhsVar))
  160. {
  161. // if (ldloc lhsVar) box bool(ldloc lhsVar) else dynamic.binary.operator.logic Or(ldloc lhsVar, rhsInst)
  162. // -> dynamic.logic.operator OrElse(ldloc lhsVar, rhsInst)
  163. if (trueInst is Box box && box.Type.IsKnownType(KnownTypeCode.Boolean))
  164. {
  165. unaryOp = System.Linq.Expressions.ExpressionType.IsTrue;
  166. trueInst = box.Argument;
  167. }
  168. else if (falseInst is Box box2 && box2.Type.IsKnownType(KnownTypeCode.Boolean))
  169. {
  170. // negate condition and swap true/false
  171. unaryOp = System.Linq.Expressions.ExpressionType.IsFalse;
  172. falseInst = trueInst;
  173. trueInst = box2.Argument;
  174. }
  175. else
  176. {
  177. return null;
  178. }
  179. }
  180. else if (condition is DynamicUnaryOperatorInstruction unary)
  181. {
  182. // if (dynamic.unary.operator IsFalse(ldloc lhsVar)) ldloc lhsVar else dynamic.binary.operator.logic And(ldloc lhsVar, rhsInst)
  183. // -> dynamic.logic.operator AndAlso(ldloc lhsVar, rhsInst)
  184. unaryOp = unary.Operation;
  185. if (!unary.Operand.MatchLdLoc(out lhsVar))
  186. return null;
  187. }
  188. else if (MatchCondition(condition, out lhsVar, out string operatorMethodName))
  189. {
  190. // if (call op_False(ldloc s)) box S(ldloc s) else dynamic.binary.operator.logic And(ldloc s, rhsInst))
  191. if (operatorMethodName == "op_True")
  192. {
  193. unaryOp = System.Linq.Expressions.ExpressionType.IsTrue;
  194. }
  195. else
  196. {
  197. Debug.Assert(operatorMethodName == "op_False");
  198. unaryOp = System.Linq.Expressions.ExpressionType.IsFalse;
  199. }
  200. var callParamType = ((Call)condition).Method.Parameters.Single().Type.SkipModifiers();
  201. if (callParamType.IsReferenceType == false)
  202. {
  203. // If lhs is a value type, eliminate the boxing instruction.
  204. if (trueInst is Box box && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(box.Type, callParamType))
  205. {
  206. trueInst = box.Argument;
  207. }
  208. else if (trueInst.OpCode == OpCode.LdcI4)
  209. {
  210. // special case, handled below in 'check trueInst'
  211. }
  212. else
  213. {
  214. return null;
  215. }
  216. }
  217. }
  218. else
  219. {
  220. return null;
  221. }
  222. // Check trueInst:
  223. DynamicUnaryOperatorInstruction rhsUnary;
  224. if (trueInst.MatchLdLoc(lhsVar))
  225. {
  226. // OK, typical pattern where the expression evaluates to 'dynamic'
  227. rhsUnary = null;
  228. }
  229. else if (trueInst.MatchLdcI4(1) && unaryOp == System.Linq.Expressions.ExpressionType.IsTrue)
  230. {
  231. // logic.or(IsTrue(lhsVar), IsTrue(lhsVar | rhsInst))
  232. // => IsTrue(lhsVar || rhsInst)
  233. rhsUnary = falseInst as DynamicUnaryOperatorInstruction;
  234. if (rhsUnary != null)
  235. {
  236. if (rhsUnary.Operation != System.Linq.Expressions.ExpressionType.IsTrue)
  237. return null;
  238. falseInst = rhsUnary.Operand;
  239. }
  240. else
  241. {
  242. return null;
  243. }
  244. }
  245. else
  246. {
  247. return null;
  248. }
  249. System.Linq.Expressions.ExpressionType expectedBitop;
  250. System.Linq.Expressions.ExpressionType logicOp;
  251. if (unaryOp == System.Linq.Expressions.ExpressionType.IsFalse)
  252. {
  253. expectedBitop = System.Linq.Expressions.ExpressionType.And;
  254. logicOp = System.Linq.Expressions.ExpressionType.AndAlso;
  255. }
  256. else if (unaryOp == System.Linq.Expressions.ExpressionType.IsTrue)
  257. {
  258. expectedBitop = System.Linq.Expressions.ExpressionType.Or;
  259. logicOp = System.Linq.Expressions.ExpressionType.OrElse;
  260. }
  261. else
  262. {
  263. return null;
  264. }
  265. // Check falseInst:
  266. if (!(falseInst is DynamicBinaryOperatorInstruction binary))
  267. return null;
  268. if (binary.Operation != expectedBitop)
  269. return null;
  270. if (!binary.Left.MatchLdLoc(lhsVar))
  271. return null;
  272. var logicInst = new DynamicLogicOperatorInstruction(binary.BinderFlags, logicOp, binary.CallingContext,
  273. binary.LeftArgumentInfo, binary.Left, binary.RightArgumentInfo, binary.Right)
  274. .WithILRange(binary);
  275. if (rhsUnary != null)
  276. {
  277. rhsUnary.Operand = logicInst;
  278. return rhsUnary;
  279. }
  280. else
  281. {
  282. return logicInst;
  283. }
  284. }
  285. }
  286. }