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.

957 lines
32 KiB

10 years ago
10 years ago
  1. // Copyright (c) 2014-2017 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.Linq.Expressions;
  23. using ICSharpCode.Decompiler.TypeSystem;
  24. namespace ICSharpCode.Decompiler.IL.Transforms
  25. {
  26. /// <summary>
  27. /// Collection of transforms that detect simple expression patterns
  28. /// (e.g. 'cgt.un(..., ld.null)') and replace them with different instructions.
  29. /// </summary>
  30. /// <remarks>
  31. /// Should run after inlining so that the expression patterns can be detected.
  32. /// </remarks>
  33. public class ExpressionTransforms : ILVisitor, IStatementTransform
  34. {
  35. internal StatementTransformContext context;
  36. public static void RunOnSingleStatement(ILInstruction statement, ILTransformContext context)
  37. {
  38. if (statement == null)
  39. throw new ArgumentNullException(nameof(statement));
  40. if (!(statement.Parent is Block parent))
  41. throw new ArgumentException("ILInstruction must be a statement, i.e., direct child of a block.");
  42. new ExpressionTransforms().Run(parent, statement.ChildIndex, new StatementTransformContext(new BlockTransformContext(context)));
  43. }
  44. public void Run(Block block, int pos, StatementTransformContext context)
  45. {
  46. this.context = context;
  47. context.StepStartGroup($"ExpressionTransforms ({block.Label}:{pos})", block.Instructions[pos]);
  48. block.Instructions[pos].AcceptVisitor(this);
  49. context.StepEndGroup(keepIfEmpty: true);
  50. }
  51. protected override void Default(ILInstruction inst)
  52. {
  53. foreach (var child in inst.Children)
  54. {
  55. child.AcceptVisitor(this);
  56. }
  57. }
  58. protected internal override void VisitBlockContainer(BlockContainer container)
  59. {
  60. if (container.Kind == ContainerKind.Switch)
  61. {
  62. // Special case for switch: Only visit the switch condition block.
  63. var switchInst = (SwitchInstruction)container.EntryPoint.Instructions[0];
  64. switchInst.Value.AcceptVisitor(this);
  65. HandleSwitchExpression(container, switchInst);
  66. }
  67. // No need to call base.VisitBlockContainer, see comment in VisitBlock.
  68. }
  69. protected internal override void VisitBlock(Block block)
  70. {
  71. if (block.Kind == BlockKind.ControlFlow)
  72. {
  73. // Don't visit child control flow blocks;
  74. // since this is a block transform
  75. // we know those were already handled previously.
  76. return;
  77. }
  78. base.VisitBlock(block);
  79. }
  80. protected internal override void VisitComp(Comp inst)
  81. {
  82. // "logic.not(arg)" is sugar for "comp(arg != ldc.i4 0)"
  83. if (inst.MatchLogicNot(out var arg))
  84. {
  85. VisitLogicNot(inst, arg);
  86. return;
  87. }
  88. else if (inst.Kind == ComparisonKind.Inequality && inst.LiftingKind == ComparisonLiftingKind.None
  89. && inst.Right.MatchLdcI4(0) && (IfInstruction.IsInConditionSlot(inst) || inst.Left is Comp)
  90. )
  91. {
  92. // if (comp(x != 0)) ==> if (x)
  93. // comp(comp(...) != 0) => comp(...)
  94. context.Step("Remove redundant comp(... != 0)", inst);
  95. inst.Left.AddILRange(inst);
  96. inst.ReplaceWith(inst.Left);
  97. inst.Left.AcceptVisitor(this);
  98. return;
  99. }
  100. base.VisitComp(inst);
  101. if (inst.IsLifted)
  102. {
  103. return;
  104. }
  105. if (inst.Right.MatchLdNull())
  106. {
  107. if (inst.Kind == ComparisonKind.GreaterThan)
  108. {
  109. context.Step("comp(left > ldnull) => comp(left != ldnull)", inst);
  110. inst.Kind = ComparisonKind.Inequality;
  111. }
  112. else if (inst.Kind == ComparisonKind.LessThanOrEqual)
  113. {
  114. context.Step("comp(left <= ldnull) => comp(left == ldnull)", inst);
  115. inst.Kind = ComparisonKind.Equality;
  116. }
  117. }
  118. else if (inst.Left.MatchLdNull())
  119. {
  120. if (inst.Kind == ComparisonKind.LessThan)
  121. {
  122. context.Step("comp(ldnull < right) => comp(ldnull != right)", inst);
  123. inst.Kind = ComparisonKind.Inequality;
  124. }
  125. else if (inst.Kind == ComparisonKind.GreaterThanOrEqual)
  126. {
  127. context.Step("comp(ldnull >= right) => comp(ldnull == right)", inst);
  128. inst.Kind = ComparisonKind.Equality;
  129. }
  130. }
  131. var rightWithoutConv = inst.Right.UnwrapConv(ConversionKind.SignExtend).UnwrapConv(ConversionKind.ZeroExtend);
  132. if (rightWithoutConv.MatchLdcI4(0)
  133. && inst.Sign == Sign.Unsigned
  134. && (inst.Kind == ComparisonKind.GreaterThan || inst.Kind == ComparisonKind.LessThanOrEqual))
  135. {
  136. if (inst.Kind == ComparisonKind.GreaterThan)
  137. {
  138. context.Step("comp.unsigned(left > ldc.i4 0) => comp(left != ldc.i4 0)", inst);
  139. inst.Kind = ComparisonKind.Inequality;
  140. VisitComp(inst);
  141. return;
  142. }
  143. else if (inst.Kind == ComparisonKind.LessThanOrEqual)
  144. {
  145. context.Step("comp.unsigned(left <= ldc.i4 0) => comp(left == ldc.i4 0)", inst);
  146. inst.Kind = ComparisonKind.Equality;
  147. VisitComp(inst);
  148. return;
  149. }
  150. }
  151. else if (rightWithoutConv.MatchLdcI4(0) && inst.Kind.IsEqualityOrInequality())
  152. {
  153. if (inst.Left.MatchLdLen(StackType.I, out ILInstruction array))
  154. {
  155. // comp.unsigned(ldlen array == conv i4->i(ldc.i4 0))
  156. // => comp(ldlen.i4 array == ldc.i4 0)
  157. // This is a special case where the C# compiler doesn't generate conv.i4 after ldlen.
  158. context.Step("comp(ldlen.i4 array == ldc.i4 0)", inst);
  159. inst.InputType = StackType.I4;
  160. inst.Left.ReplaceWith(new LdLen(StackType.I4, array).WithILRange(inst.Left));
  161. inst.Right = rightWithoutConv;
  162. }
  163. else if (inst.Left is Conv conv && conv.TargetType == PrimitiveType.I && conv.Argument.ResultType == StackType.O)
  164. {
  165. // C++/CLI sometimes uses this weird comparison with null:
  166. context.Step("comp(conv o->i (ldloc obj) == conv i4->i <sign extend>(ldc.i4 0))", inst);
  167. // -> comp(ldloc obj == ldnull)
  168. inst.InputType = StackType.O;
  169. inst.Left = conv.Argument;
  170. inst.Right = new LdNull().WithILRange(inst.Right);
  171. inst.Right.AddILRange(rightWithoutConv);
  172. }
  173. }
  174. if (inst.Right.MatchLdNull() && inst.Left.MatchBox(out arg, out var type) && type.Kind == TypeKind.TypeParameter)
  175. {
  176. if (inst.Kind == ComparisonKind.Equality)
  177. {
  178. context.Step("comp(box T(..) == ldnull) -> comp(.. == ldnull)", inst);
  179. inst.Left = arg;
  180. }
  181. if (inst.Kind == ComparisonKind.Inequality)
  182. {
  183. context.Step("comp(box T(..) != ldnull) -> comp(.. != ldnull)", inst);
  184. inst.Left = arg;
  185. }
  186. }
  187. }
  188. protected internal override void VisitConv(Conv inst)
  189. {
  190. inst.Argument.AcceptVisitor(this);
  191. if (inst.Argument.MatchLdLen(StackType.I, out ILInstruction array) && inst.TargetType.IsIntegerType()
  192. && (!inst.CheckForOverflow || context.Settings.AssumeArrayLengthFitsIntoInt32))
  193. {
  194. context.Step("conv.i4(ldlen array) => ldlen.i4(array)", inst);
  195. inst.AddILRange(inst.Argument);
  196. inst.ReplaceWith(new LdLen(inst.TargetType.GetStackType(), array).WithILRange(inst));
  197. return;
  198. }
  199. if (inst.TargetType.IsFloatType() && inst.Argument is Conv conv
  200. && conv.Kind == ConversionKind.IntToFloat && conv.TargetType == PrimitiveType.R)
  201. {
  202. // IL conv.r.un does not indicate whether to convert the target type to R4 or R8,
  203. // so the C# compiler usually follows it with an explicit conv.r4 or conv.r8.
  204. // To avoid emitting '(float)(double)val', we combine these two conversions:
  205. context.Step("conv.rN(conv.r.un(...)) => conv.rN.un(...)", inst);
  206. inst.ReplaceWith(new Conv(conv.Argument, conv.InputType, conv.InputSign, inst.TargetType, inst.CheckForOverflow, inst.IsLifted | conv.IsLifted));
  207. return;
  208. }
  209. }
  210. protected internal override void VisitBox(Box inst)
  211. {
  212. inst.Argument.AcceptVisitor(this);
  213. if (inst.Type.IsReferenceType == true && inst.Argument.ResultType == inst.ResultType)
  214. {
  215. // For reference types, box is a no-op.
  216. context.Step("box ref-type(arg) => arg", inst);
  217. inst.Argument.AddILRange(inst);
  218. inst.ReplaceWith(inst.Argument);
  219. }
  220. }
  221. protected internal override void VisitLdElema(LdElema inst)
  222. {
  223. base.VisitLdElema(inst);
  224. CleanUpArrayIndices(inst.Indices);
  225. if (IndexRangeTransform.HandleLdElema(inst, context))
  226. return;
  227. }
  228. protected internal override void VisitNewArr(NewArr inst)
  229. {
  230. base.VisitNewArr(inst);
  231. CleanUpArrayIndices(inst.Indices);
  232. }
  233. void CleanUpArrayIndices(InstructionCollection<ILInstruction> indices)
  234. {
  235. foreach (ILInstruction index in indices)
  236. {
  237. if (index is Conv conv && conv.ResultType == StackType.I
  238. && (conv.Kind == ConversionKind.Truncate && conv.CheckForOverflow
  239. || conv.Kind == ConversionKind.ZeroExtend || conv.Kind == ConversionKind.SignExtend)
  240. )
  241. {
  242. context.Step("Remove conv.i from array index", index);
  243. index.ReplaceWith(conv.Argument);
  244. }
  245. }
  246. }
  247. void VisitLogicNot(Comp inst, ILInstruction arg)
  248. {
  249. ILInstruction lhs, rhs;
  250. if (arg is Comp comp)
  251. {
  252. if ((!comp.InputType.IsFloatType() && !comp.IsLifted) || comp.Kind.IsEqualityOrInequality())
  253. {
  254. context.Step("push negation into comparison", inst);
  255. comp.Kind = comp.Kind.Negate();
  256. comp.AddILRange(inst);
  257. inst.ReplaceWith(comp);
  258. }
  259. comp.AcceptVisitor(this);
  260. }
  261. else if (arg.MatchLogicAnd(out lhs, out rhs))
  262. {
  263. // logic.not(if (lhs) rhs else ldc.i4 0)
  264. // ==> if (logic.not(lhs)) ldc.i4 1 else logic.not(rhs)
  265. context.Step("push negation into logic.and", inst);
  266. IfInstruction ifInst = (IfInstruction)arg;
  267. var ldc0 = ifInst.FalseInst;
  268. Debug.Assert(ldc0.MatchLdcI4(0));
  269. ifInst.Condition = Comp.LogicNot(lhs).WithILRange(inst);
  270. ifInst.TrueInst = new LdcI4(1).WithILRange(ldc0);
  271. ifInst.FalseInst = Comp.LogicNot(rhs).WithILRange(inst);
  272. inst.ReplaceWith(ifInst);
  273. ifInst.AcceptVisitor(this);
  274. }
  275. else if (arg.MatchLogicOr(out lhs, out rhs))
  276. {
  277. // logic.not(if (lhs) ldc.i4 1 else rhs)
  278. // ==> if (logic.not(lhs)) logic.not(rhs) else ldc.i4 0)
  279. context.Step("push negation into logic.or", inst);
  280. IfInstruction ifInst = (IfInstruction)arg;
  281. var ldc1 = ifInst.TrueInst;
  282. Debug.Assert(ldc1.MatchLdcI4(1));
  283. ifInst.Condition = Comp.LogicNot(lhs).WithILRange(inst);
  284. ifInst.TrueInst = Comp.LogicNot(rhs).WithILRange(inst);
  285. ifInst.FalseInst = new LdcI4(0).WithILRange(ldc1);
  286. inst.ReplaceWith(ifInst);
  287. ifInst.AcceptVisitor(this);
  288. }
  289. else
  290. {
  291. arg.AcceptVisitor(this);
  292. }
  293. }
  294. protected internal override void VisitCall(Call inst)
  295. {
  296. base.VisitCall(inst);
  297. TransformAssignment.HandleCompoundAssign(inst, context);
  298. }
  299. protected internal override void VisitCallVirt(CallVirt inst)
  300. {
  301. base.VisitCallVirt(inst);
  302. TransformAssignment.HandleCompoundAssign(inst, context);
  303. }
  304. protected internal override void VisitNewObj(NewObj inst)
  305. {
  306. if (TransformDecimalCtorToConstant(inst, out LdcDecimal decimalConstant))
  307. {
  308. context.Step("TransformDecimalCtorToConstant", inst);
  309. inst.ReplaceWith(decimalConstant);
  310. return;
  311. }
  312. Block block;
  313. if (TransformSpanTCtorContainingStackAlloc(inst, out ILInstruction locallocSpan))
  314. {
  315. context.Step("new Span<T>(stackalloc) -> stackalloc Span<T>", inst);
  316. inst.ReplaceWith(locallocSpan);
  317. block = null;
  318. ILInstruction stmt = locallocSpan;
  319. while (stmt.Parent != null)
  320. {
  321. if (stmt.Parent is Block b)
  322. {
  323. block = b;
  324. break;
  325. }
  326. stmt = stmt.Parent;
  327. }
  328. // Special case to eliminate extra store
  329. if (stmt.GetNextSibling() is StLoc storeStmt && storeStmt.Value is LdLoc)
  330. ILInlining.InlineIfPossible(block, stmt.ChildIndex, context);
  331. return;
  332. }
  333. if (TransformArrayInitializers.TransformSpanTArrayInitialization(inst, context, out block))
  334. {
  335. context.Step("TransformSpanTArrayInitialization: single-dim", inst);
  336. inst.ReplaceWith(block);
  337. return;
  338. }
  339. if (TransformDelegateCtorLdVirtFtnToLdVirtDelegate(inst, out LdVirtDelegate ldVirtDelegate))
  340. {
  341. context.Step("new Delegate(target, ldvirtftn Method) -> ldvirtdelegate Delegate Method(target)", inst);
  342. inst.ReplaceWith(ldVirtDelegate);
  343. return;
  344. }
  345. base.VisitNewObj(inst);
  346. }
  347. /// <summary>
  348. /// newobj Delegate..ctor(target, ldvirtftn TargetMethod(target))
  349. /// =>
  350. /// ldvirtdelegate System.Delegate TargetMethod(target)
  351. /// </summary>
  352. bool TransformDelegateCtorLdVirtFtnToLdVirtDelegate(NewObj inst, out LdVirtDelegate ldVirtDelegate)
  353. {
  354. ldVirtDelegate = null;
  355. if (inst.Method.DeclaringType.Kind != TypeKind.Delegate)
  356. return false;
  357. if (inst.Arguments.Count != 2)
  358. return false;
  359. if (!(inst.Arguments[1] is LdVirtFtn ldVirtFtn))
  360. return false;
  361. if (!SemanticHelper.IsPure(inst.Arguments[0].Flags))
  362. return false;
  363. if (!inst.Arguments[0].Match(ldVirtFtn.Argument).Success)
  364. return false;
  365. ldVirtDelegate = new LdVirtDelegate(inst.Arguments[0], inst.Method.DeclaringType, ldVirtFtn.Method)
  366. .WithILRange(inst).WithILRange(ldVirtFtn).WithILRange(ldVirtFtn.Argument);
  367. return true;
  368. }
  369. /// <summary>
  370. /// newobj Span..ctor(localloc(conv i4->u &lt;zero extend&gt;(ldc.i4 sizeInBytes)), numberOfElementsExpr)
  371. /// =>
  372. /// localloc.span T(numberOfElementsExpr)
  373. ///
  374. /// -or-
  375. ///
  376. /// newobj Span..ctor(Block IL_0000 (StackAllocInitializer) {
  377. /// stloc I_0(localloc(conv i4->u&lt;zero extend>(ldc.i4 sizeInBytes)))
  378. /// ...
  379. /// final: ldloc I_0
  380. /// }, numberOfElementsExpr)
  381. /// =>
  382. /// Block IL_0000 (StackAllocInitializer) {
  383. /// stloc I_0(localloc.span T(numberOfElementsExpr))
  384. /// ...
  385. /// final: ldloc I_0
  386. /// }
  387. /// </summary>
  388. bool TransformSpanTCtorContainingStackAlloc(NewObj newObj, out ILInstruction locallocSpan)
  389. {
  390. locallocSpan = null;
  391. IType type = newObj.Method.DeclaringType;
  392. if (!type.IsKnownType(KnownTypeCode.SpanOfT) && !type.IsKnownType(KnownTypeCode.ReadOnlySpanOfT))
  393. return false;
  394. if (newObj.Arguments.Count != 2 || type.TypeArguments.Count != 1)
  395. return false;
  396. IType elementType = type.TypeArguments[0];
  397. if (newObj.Arguments[0].MatchLocAlloc(out var sizeInBytes) && MatchesElementCount(sizeInBytes, elementType, newObj.Arguments[1]))
  398. {
  399. locallocSpan = new LocAllocSpan(newObj.Arguments[1], type);
  400. return true;
  401. }
  402. if (newObj.Arguments[0] is Block initializer && initializer.Kind == BlockKind.StackAllocInitializer)
  403. {
  404. if (!initializer.Instructions[0].MatchStLoc(out var initializerVariable, out var value))
  405. return false;
  406. if (!(value.MatchLocAlloc(out sizeInBytes) && MatchesElementCount(sizeInBytes, elementType, newObj.Arguments[1])))
  407. return false;
  408. var newVariable = initializerVariable.Function.RegisterVariable(VariableKind.InitializerTarget, type);
  409. foreach (var load in initializerVariable.LoadInstructions.ToArray())
  410. {
  411. ILInstruction newInst = new LdLoc(newVariable);
  412. newInst.AddILRange(load);
  413. if (load.Parent != initializer)
  414. newInst = new Conv(newInst, PrimitiveType.I, false, Sign.None);
  415. load.ReplaceWith(newInst);
  416. }
  417. foreach (var store in initializerVariable.StoreInstructions.ToArray())
  418. {
  419. store.Variable = newVariable;
  420. }
  421. value.ReplaceWith(new LocAllocSpan(newObj.Arguments[1], type));
  422. locallocSpan = initializer;
  423. return true;
  424. }
  425. return false;
  426. }
  427. bool MatchesElementCount(ILInstruction sizeInBytesInstr, IType elementType, ILInstruction elementCountInstr2)
  428. {
  429. var pointerType = new PointerType(elementType);
  430. var elementCountInstr = PointerArithmeticOffset.Detect(sizeInBytesInstr, pointerType.ElementType, checkForOverflow: true, unwrapZeroExtension: true);
  431. if (elementCountInstr == null || !elementCountInstr.UnwrapConv(ConversionKind.ZeroExtend).Match(elementCountInstr2).Success)
  432. return false;
  433. return true;
  434. }
  435. bool TransformDecimalCtorToConstant(NewObj inst, out LdcDecimal result)
  436. {
  437. IType t = inst.Method.DeclaringType;
  438. result = null;
  439. if (!t.IsKnownType(KnownTypeCode.Decimal))
  440. return false;
  441. var args = inst.Arguments;
  442. if (args.Count == 1)
  443. {
  444. int val;
  445. if (args[0].MatchLdcI4(out val))
  446. {
  447. result = new LdcDecimal(val);
  448. return true;
  449. }
  450. }
  451. else if (args.Count == 5)
  452. {
  453. int lo, mid, hi, isNegative, scale;
  454. if (args[0].MatchLdcI4(out lo) && args[1].MatchLdcI4(out mid) &&
  455. args[2].MatchLdcI4(out hi) && args[3].MatchLdcI4(out isNegative) &&
  456. args[4].MatchLdcI4(out scale))
  457. {
  458. result = new LdcDecimal(new decimal(lo, mid, hi, isNegative != 0, (byte)scale));
  459. return true;
  460. }
  461. }
  462. return false;
  463. }
  464. bool TransformDecimalFieldToConstant(LdObj inst, out LdcDecimal result)
  465. {
  466. if (inst.MatchLdsFld(out var field) && field.DeclaringType.IsKnownType(KnownTypeCode.Decimal))
  467. {
  468. decimal? value = null;
  469. if (field.Name == "One")
  470. {
  471. value = decimal.One;
  472. }
  473. else if (field.Name == "MinusOne")
  474. {
  475. value = decimal.MinusOne;
  476. }
  477. else if (field.Name == "Zero")
  478. {
  479. value = decimal.Zero;
  480. }
  481. if (value != null)
  482. {
  483. result = new LdcDecimal(value.Value).WithILRange(inst).WithILRange(inst.Target);
  484. return true;
  485. }
  486. }
  487. result = null;
  488. return false;
  489. }
  490. protected internal override void VisitLdObj(LdObj inst)
  491. {
  492. base.VisitLdObj(inst);
  493. EarlyExpressionTransforms.AddressOfLdLocToLdLoca(inst, context);
  494. if (EarlyExpressionTransforms.LdObjToLdLoc(inst, context))
  495. return;
  496. if (TransformDecimalFieldToConstant(inst, out LdcDecimal decimalConstant))
  497. {
  498. context.Step("TransformDecimalFieldToConstant", inst);
  499. inst.ReplaceWith(decimalConstant);
  500. return;
  501. }
  502. }
  503. protected internal override void VisitStObj(StObj inst)
  504. {
  505. base.VisitStObj(inst);
  506. if (EarlyExpressionTransforms.StObjToStLoc(inst, context))
  507. {
  508. context.RequestRerun();
  509. return;
  510. }
  511. TransformAssignment.HandleCompoundAssign(inst, context);
  512. }
  513. protected internal override void VisitStLoc(StLoc inst)
  514. {
  515. base.VisitStLoc(inst);
  516. TransformAssignment.HandleCompoundAssign(inst, context);
  517. }
  518. protected internal override void VisitIfInstruction(IfInstruction inst)
  519. {
  520. inst.TrueInst.AcceptVisitor(this);
  521. inst.FalseInst.AcceptVisitor(this);
  522. inst = HandleConditionalOperator(inst);
  523. // Bring LogicAnd/LogicOr into their canonical forms:
  524. // if (cond) ldc.i4 0 else RHS --> if (!cond) RHS else ldc.i4 0
  525. // if (cond) RHS else ldc.i4 1 --> if (!cond) ldc.i4 1 else RHS
  526. // Be careful: when both LHS and RHS are the constant 1, we must not
  527. // swap the arguments as it would lead to an infinite transform loop.
  528. if (inst.TrueInst.MatchLdcI4(0) && !inst.FalseInst.MatchLdcI4(0)
  529. || inst.FalseInst.MatchLdcI4(1) && !inst.TrueInst.MatchLdcI4(1))
  530. {
  531. context.Step("canonicalize logic and/or", inst);
  532. var t = inst.TrueInst;
  533. inst.TrueInst = inst.FalseInst;
  534. inst.FalseInst = t;
  535. inst.Condition = Comp.LogicNot(inst.Condition);
  536. }
  537. // Process condition after our potential modifications.
  538. inst.Condition.AcceptVisitor(this);
  539. if (new NullableLiftingTransform(context).Run(inst))
  540. return;
  541. if (TransformDynamicAddAssignOrRemoveAssign(inst))
  542. return;
  543. if (inst.MatchIfInstructionPositiveCondition(out var condition, out var trueInst, out var falseInst))
  544. {
  545. ILInstruction transformed = UserDefinedLogicTransform.Transform(condition, trueInst, falseInst);
  546. if (transformed == null)
  547. {
  548. transformed = UserDefinedLogicTransform.TransformDynamic(condition, trueInst, falseInst);
  549. }
  550. if (transformed != null)
  551. {
  552. context.Step("User-defined short-circuiting logic operator (roslyn pattern)", condition);
  553. transformed.AddILRange(inst);
  554. inst.ReplaceWith(transformed);
  555. return;
  556. }
  557. }
  558. }
  559. IfInstruction HandleConditionalOperator(IfInstruction inst)
  560. {
  561. // if (cond) stloc (A, V1) else stloc (A, V2) --> stloc (A, if (cond) V1 else V2)
  562. Block trueInst = inst.TrueInst as Block;
  563. if (trueInst == null || trueInst.Instructions.Count != 1)
  564. return inst;
  565. Block falseInst = inst.FalseInst as Block;
  566. if (falseInst == null || falseInst.Instructions.Count != 1)
  567. return inst;
  568. ILVariable v;
  569. ILInstruction value1, value2;
  570. if (trueInst.Instructions[0].MatchStLoc(out v, out value1) && falseInst.Instructions[0].MatchStLoc(v, out value2))
  571. {
  572. context.Step("conditional operator", inst);
  573. var newIf = new IfInstruction(Comp.LogicNot(inst.Condition), value2, value1);
  574. newIf.AddILRange(inst);
  575. inst.ReplaceWith(new StLoc(v, newIf));
  576. context.RequestRerun(); // trigger potential inlining of the newly created StLoc
  577. return newIf;
  578. }
  579. return inst;
  580. }
  581. private void HandleSwitchExpression(BlockContainer container, SwitchInstruction switchInst)
  582. {
  583. if (!context.Settings.SwitchExpressions)
  584. return;
  585. Debug.Assert(container.Kind == ContainerKind.Switch);
  586. Debug.Assert(container.ResultType == StackType.Void);
  587. var defaultSection = switchInst.GetDefaultSection();
  588. StackType resultType = StackType.Void;
  589. BlockContainer leaveTarget = null;
  590. ILVariable resultVariable = null;
  591. foreach (var section in switchInst.Sections)
  592. {
  593. if (section != defaultSection)
  594. {
  595. // every section except for the default must have exactly 1 label
  596. if (section.Labels.Count() != (section.HasNullLabel ? 0u : 1u))
  597. return;
  598. }
  599. if (!section.Body.MatchBranch(out var sectionBlock))
  600. return;
  601. if (sectionBlock.IncomingEdgeCount != 1)
  602. return;
  603. if (sectionBlock.Parent != container)
  604. return;
  605. if (sectionBlock.Instructions.Count == 1)
  606. {
  607. if (sectionBlock.Instructions[0] is Throw)
  608. {
  609. // OK
  610. }
  611. else if (sectionBlock.Instructions[0] is Leave leave)
  612. {
  613. if (!leave.IsLeavingFunction)
  614. return;
  615. leaveTarget ??= leave.TargetContainer;
  616. Debug.Assert(leaveTarget == leave.TargetContainer);
  617. resultType = leave.Value.ResultType;
  618. }
  619. else
  620. {
  621. return;
  622. }
  623. }
  624. else if (sectionBlock.Instructions.Count == 2)
  625. {
  626. if (!sectionBlock.Instructions[0].MatchStLoc(out var v, out _))
  627. return;
  628. if (!sectionBlock.Instructions[1].MatchLeave(container))
  629. return;
  630. resultVariable ??= v;
  631. if (resultVariable != v)
  632. return;
  633. resultType = resultVariable.StackType;
  634. }
  635. else
  636. {
  637. return;
  638. }
  639. }
  640. // Exactly one of resultVariable/leaveTarget must be null
  641. if ((resultVariable == null) == (leaveTarget == null))
  642. return;
  643. if (switchInst.Value is StringToInt str2int)
  644. {
  645. // validate that each integer is used for exactly one value
  646. var integersUsed = new HashSet<int>();
  647. foreach ((string key, int val) in str2int.Map)
  648. {
  649. if (!integersUsed.Add(val))
  650. return;
  651. }
  652. }
  653. context.Step("Switch Expression", switchInst);
  654. switchInst.SetResultType(resultType);
  655. foreach (var section in switchInst.Sections)
  656. {
  657. var block = ((Branch)section.Body).TargetBlock;
  658. if (block.Instructions.Count == 1)
  659. {
  660. if (block.Instructions[0] is Throw t)
  661. {
  662. t.resultType = resultType;
  663. section.Body = t;
  664. }
  665. else if (block.Instructions[0] is Leave leave)
  666. {
  667. section.Body = leave.Value;
  668. }
  669. else
  670. {
  671. throw new InvalidOperationException();
  672. }
  673. }
  674. else
  675. {
  676. section.Body = ((StLoc)block.Instructions[0]).Value;
  677. }
  678. }
  679. if (resultVariable != null)
  680. {
  681. container.ReplaceWith(new StLoc(resultVariable, switchInst));
  682. }
  683. else
  684. {
  685. container.ReplaceWith(new Leave(leaveTarget, switchInst));
  686. }
  687. context.RequestRerun(); // new StLoc might trigger inlining
  688. }
  689. /// <summary>
  690. /// op is either add or remove/subtract:
  691. /// if (dynamic.isevent (target)) {
  692. /// dynamic.invokemember.invokespecial.discard op_Name(target, value)
  693. /// } else {
  694. /// dynamic.compound.op (dynamic.getmember Name(target), value)
  695. /// }
  696. /// =>
  697. /// dynamic.compound.op (dynamic.getmember Name(target), value)
  698. /// </summary>
  699. bool TransformDynamicAddAssignOrRemoveAssign(IfInstruction inst)
  700. {
  701. if (!inst.MatchIfInstructionPositiveCondition(out var condition, out var trueInst, out var falseInst))
  702. return false;
  703. if (!(condition is DynamicIsEventInstruction isEvent))
  704. return false;
  705. trueInst = Block.Unwrap(trueInst);
  706. falseInst = Block.Unwrap(falseInst);
  707. if (!(falseInst is DynamicCompoundAssign dynamicCompoundAssign))
  708. return false;
  709. if (!(dynamicCompoundAssign.Target is DynamicGetMemberInstruction getMember))
  710. return false;
  711. if (!SemanticHelper.IsPure(isEvent.Argument.Flags))
  712. return false;
  713. if (!isEvent.Argument.Match(getMember.Target).Success)
  714. return false;
  715. if (!(trueInst is DynamicInvokeMemberInstruction invokeMember))
  716. return false;
  717. if (!(invokeMember.BinderFlags.HasFlag(CSharpBinderFlags.InvokeSpecialName) && invokeMember.BinderFlags.HasFlag(CSharpBinderFlags.ResultDiscarded)))
  718. return false;
  719. switch (dynamicCompoundAssign.Operation)
  720. {
  721. case ExpressionType.AddAssign:
  722. if (invokeMember.Name != "add_" + getMember.Name)
  723. return false;
  724. break;
  725. case ExpressionType.SubtractAssign:
  726. if (invokeMember.Name != "remove_" + getMember.Name)
  727. return false;
  728. break;
  729. default:
  730. return false;
  731. }
  732. if (!dynamicCompoundAssign.Value.Match(invokeMember.Arguments[1]).Success)
  733. return false;
  734. if (!invokeMember.Arguments[0].Match(getMember.Target).Success)
  735. return false;
  736. context.Step("+= / -= dynamic.isevent pattern -> dynamic.compound.op", inst);
  737. inst.ReplaceWith(dynamicCompoundAssign);
  738. return true;
  739. }
  740. /// <summary>
  741. /// dynamic.setmember.compound Name(target, dynamic.binary.operator op(dynamic.getmember Name(target), value))
  742. /// =>
  743. /// dynamic.compound.op (dynamic.getmember Name(target), value)
  744. /// </summary>
  745. protected internal override void VisitDynamicSetMemberInstruction(DynamicSetMemberInstruction inst)
  746. {
  747. base.VisitDynamicSetMemberInstruction(inst);
  748. TransformDynamicSetMemberInstruction(inst, context);
  749. }
  750. internal static void TransformDynamicSetMemberInstruction(DynamicSetMemberInstruction inst, StatementTransformContext context)
  751. {
  752. if (!inst.BinderFlags.HasFlag(CSharpBinderFlags.ValueFromCompoundAssignment))
  753. return;
  754. if (!(inst.Value is DynamicBinaryOperatorInstruction binaryOp))
  755. return;
  756. if (!(binaryOp.Left is DynamicGetMemberInstruction dynamicGetMember))
  757. return;
  758. if (!dynamicGetMember.Target.Match(inst.Target).Success)
  759. return;
  760. if (!SemanticHelper.IsPure(dynamicGetMember.Target.Flags))
  761. return;
  762. if (inst.Name != dynamicGetMember.Name || !DynamicCompoundAssign.IsExpressionTypeSupported(binaryOp.Operation))
  763. return;
  764. context.Step("dynamic.setmember.compound -> dynamic.compound.op", inst);
  765. inst.ReplaceWith(new DynamicCompoundAssign(binaryOp.Operation, binaryOp.BinderFlags, binaryOp.Left, binaryOp.LeftArgumentInfo, binaryOp.Right, binaryOp.RightArgumentInfo));
  766. }
  767. /// <summary>
  768. /// dynamic.setindex.compound(target, index, dynamic.binary.operator op(dynamic.getindex(target, index), value))
  769. /// =>
  770. /// dynamic.compound.op (dynamic.getindex(target, index), value)
  771. /// </summary>
  772. protected internal override void VisitDynamicSetIndexInstruction(DynamicSetIndexInstruction inst)
  773. {
  774. base.VisitDynamicSetIndexInstruction(inst);
  775. if (!inst.BinderFlags.HasFlag(CSharpBinderFlags.ValueFromCompoundAssignment))
  776. return;
  777. if (!(inst.Arguments.LastOrDefault() is DynamicBinaryOperatorInstruction binaryOp))
  778. return;
  779. if (!(binaryOp.Left is DynamicGetIndexInstruction dynamicGetIndex))
  780. return;
  781. if (inst.Arguments.Count != dynamicGetIndex.Arguments.Count + 1)
  782. return;
  783. // Ensure that same arguments are passed to dynamicGetIndex and inst:
  784. for (int j = 0; j < dynamicGetIndex.Arguments.Count; j++)
  785. {
  786. if (!SemanticHelper.IsPure(dynamicGetIndex.Arguments[j].Flags))
  787. return;
  788. if (!dynamicGetIndex.Arguments[j].Match(inst.Arguments[j]).Success)
  789. return;
  790. }
  791. if (!DynamicCompoundAssign.IsExpressionTypeSupported(binaryOp.Operation))
  792. return;
  793. context.Step("dynamic.setindex.compound -> dynamic.compound.op", inst);
  794. inst.ReplaceWith(new DynamicCompoundAssign(binaryOp.Operation, binaryOp.BinderFlags, binaryOp.Left, binaryOp.LeftArgumentInfo, binaryOp.Right, binaryOp.RightArgumentInfo));
  795. }
  796. protected internal override void VisitBinaryNumericInstruction(BinaryNumericInstruction inst)
  797. {
  798. base.VisitBinaryNumericInstruction(inst);
  799. switch (inst.Operator)
  800. {
  801. case BinaryNumericOperator.ShiftLeft:
  802. case BinaryNumericOperator.ShiftRight:
  803. if (inst.Right.MatchBinaryNumericInstruction(BinaryNumericOperator.BitAnd, out var lhs, out var rhs)
  804. && rhs.MatchLdcI4(inst.ResultType == StackType.I8 ? 63 : 31))
  805. {
  806. // a << (b & 31) => a << b
  807. context.Step("Combine bit.and into shift", inst);
  808. inst.Right = lhs;
  809. }
  810. break;
  811. case BinaryNumericOperator.BitAnd:
  812. if (inst.Left.InferType(context.TypeSystem).IsKnownType(KnownTypeCode.Boolean)
  813. && inst.Right.InferType(context.TypeSystem).IsKnownType(KnownTypeCode.Boolean))
  814. {
  815. if (new NullableLiftingTransform(context).Run(inst))
  816. {
  817. // e.g. "(a.GetValueOrDefault() == b.GetValueOrDefault()) & (a.HasValue & b.HasValue)"
  818. }
  819. }
  820. break;
  821. }
  822. }
  823. protected internal override void VisitTryCatchHandler(TryCatchHandler inst)
  824. {
  825. base.VisitTryCatchHandler(inst);
  826. if (inst.Filter is BlockContainer filterContainer && filterContainer.Blocks.Count == 1)
  827. {
  828. TransformCatchWhen(inst, filterContainer.EntryPoint);
  829. }
  830. if (inst.Body is BlockContainer catchContainer)
  831. TransformCatchVariable(inst, catchContainer.EntryPoint, isCatchBlock: true);
  832. }
  833. /// <summary>
  834. /// catch ex : TException when (...) BlockContainer {
  835. /// Block entryPoint (incoming: 1) {
  836. /// stloc v(ldloc ex)
  837. /// ...
  838. /// }
  839. /// }
  840. /// =>
  841. /// catch v : TException when (...) BlockContainer {
  842. /// Block entryPoint (incoming: 1) {
  843. /// ...
  844. /// }
  845. /// }
  846. /// </summary>
  847. void TransformCatchVariable(TryCatchHandler handler, Block entryPoint, bool isCatchBlock)
  848. {
  849. if (!handler.Variable.IsSingleDefinition || handler.Variable.LoadCount != 1)
  850. return; // handler.Variable already has non-trivial uses
  851. if (!entryPoint.Instructions[0].MatchStLoc(out var exceptionVar, out var exceptionSlotLoad))
  852. {
  853. // Not the pattern with a second exceptionVar.
  854. // However, it is still possible that we need to remove a pointless UnboxAny:
  855. if (handler.Variable.LoadInstructions.Single().Parent is UnboxAny inlinedUnboxAny)
  856. {
  857. if (inlinedUnboxAny.Type.Equals(handler.Variable.Type))
  858. {
  859. context.Step("TransformCatchVariable - remove inlined UnboxAny", inlinedUnboxAny);
  860. inlinedUnboxAny.ReplaceWith(inlinedUnboxAny.Argument);
  861. foreach (var range in inlinedUnboxAny.ILRanges)
  862. handler.AddExceptionSpecifierILRange(range);
  863. }
  864. }
  865. return;
  866. }
  867. if (exceptionVar.Kind != VariableKind.Local && exceptionVar.Kind != VariableKind.StackSlot)
  868. return;
  869. if (exceptionSlotLoad is UnboxAny unboxAny)
  870. {
  871. // When catching a type parameter, csc emits an unbox.any instruction
  872. if (!unboxAny.Type.Equals(handler.Variable.Type))
  873. return;
  874. exceptionSlotLoad = unboxAny.Argument;
  875. }
  876. if (!exceptionSlotLoad.MatchLdLoc(handler.Variable))
  877. return;
  878. // Check that exceptionVar is only used within the catch block:
  879. var allUses = exceptionVar.LoadInstructions
  880. .Concat(exceptionVar.StoreInstructions.Cast<ILInstruction>())
  881. .Concat(exceptionVar.AddressInstructions);
  882. foreach (var inst in allUses)
  883. {
  884. if (!inst.IsDescendantOf(handler))
  885. return;
  886. }
  887. context.Step("TransformCatchVariable", entryPoint.Instructions[0]);
  888. exceptionVar.Kind = VariableKind.ExceptionLocal;
  889. exceptionVar.Type = handler.Variable.Type;
  890. handler.Variable = exceptionVar;
  891. if (isCatchBlock)
  892. {
  893. foreach (var offset in entryPoint.Instructions[0].Descendants.SelectMany(o => o.ILRanges))
  894. handler.AddExceptionSpecifierILRange(offset);
  895. }
  896. entryPoint.Instructions.RemoveAt(0);
  897. }
  898. /// <summary>
  899. /// Inline condition from catch-when condition BlockContainer, if possible.
  900. /// </summary>
  901. void TransformCatchWhen(TryCatchHandler handler, Block entryPoint)
  902. {
  903. TransformCatchVariable(handler, entryPoint, isCatchBlock: false);
  904. if (entryPoint.Instructions.Count == 1 && entryPoint.Instructions[0].MatchLeave(out _, out var condition))
  905. {
  906. context.Step("TransformCatchWhen", entryPoint.Instructions[0]);
  907. handler.Filter = condition;
  908. }
  909. }
  910. }
  911. }