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.

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