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.

732 lines
22 KiB

  1. // Copyright (c) 2021 Daniel Grunwald, Siegfried Pammer
  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. #nullable enable
  19. using System;
  20. using System.Diagnostics.CodeAnalysis;
  21. using System.Linq;
  22. using System.Reflection;
  23. using ICSharpCode.Decompiler.IL.ControlFlow;
  24. using ICSharpCode.Decompiler.TypeSystem;
  25. using ICSharpCode.Decompiler.Util;
  26. namespace ICSharpCode.Decompiler.IL.Transforms
  27. {
  28. class PatternMatchingTransform : IILTransform
  29. {
  30. void IILTransform.Run(ILFunction function, ILTransformContext context)
  31. {
  32. if (!context.Settings.PatternMatching)
  33. return;
  34. foreach (var container in function.Descendants.OfType<BlockContainer>())
  35. {
  36. ControlFlowGraph? cfg = null;
  37. foreach (var block in container.Blocks.Reverse())
  38. {
  39. if (PatternMatchValueTypes(block, container, context, ref cfg))
  40. {
  41. continue;
  42. }
  43. if (PatternMatchRefTypes(block, container, context, ref cfg))
  44. {
  45. continue;
  46. }
  47. }
  48. container.Blocks.RemoveAll(b => b.Instructions.Count == 0);
  49. }
  50. }
  51. /// Block {
  52. /// ...
  53. /// stloc V(isinst T(testedOperand))
  54. /// if (comp.o(ldloc V == ldnull)) br falseBlock
  55. /// br trueBlock
  56. /// }
  57. ///
  58. /// All other uses of V are in blocks dominated by trueBlock.
  59. /// =>
  60. /// Block {
  61. /// ...
  62. /// if (match.type[T].notnull(V = testedOperand)) br trueBlock
  63. /// br falseBlock
  64. /// }
  65. ///
  66. /// - or -
  67. ///
  68. /// Block {
  69. /// stloc s(isinst T(testedOperand))
  70. /// stloc v(ldloc s)
  71. /// if (logic.not(comp.o(ldloc s != ldnull))) br falseBlock
  72. /// br trueBlock
  73. /// }
  74. /// =>
  75. /// Block {
  76. /// ...
  77. /// if (match.type[T].notnull(V = testedOperand)) br trueBlock
  78. /// br falseBlock
  79. /// }
  80. ///
  81. /// All other uses of V are in blocks dominated by trueBlock.
  82. private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTransformContext context, ref ControlFlowGraph? cfg)
  83. {
  84. if (!block.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst))
  85. return false;
  86. int pos = block.Instructions.Count - 3;
  87. if (condition.MatchLdLoc(out var conditionVar))
  88. {
  89. // stloc conditionVar(comp.o(ldloc s == ldnull))
  90. // if (logic.not(ldloc conditionVar)) br trueBlock
  91. if (pos < 0)
  92. return false;
  93. if (!(conditionVar.IsSingleDefinition && conditionVar.LoadCount == 1
  94. && conditionVar.Kind == VariableKind.StackSlot))
  95. {
  96. return false;
  97. }
  98. if (!block.Instructions[pos].MatchStLoc(conditionVar, out condition))
  99. return false;
  100. pos--;
  101. }
  102. if (condition.MatchCompEqualsNull(out var loadInNullCheck))
  103. {
  104. ExtensionMethods.Swap(ref trueInst, ref falseInst);
  105. }
  106. else if (condition.MatchCompNotEqualsNull(out loadInNullCheck))
  107. {
  108. // do nothing
  109. }
  110. else
  111. {
  112. return false;
  113. }
  114. if (!loadInNullCheck.MatchLdLoc(out var s))
  115. return false;
  116. if (!s.IsSingleDefinition)
  117. return false;
  118. if (s.Kind is not (VariableKind.Local or VariableKind.StackSlot))
  119. return false;
  120. if (pos < 0)
  121. return false;
  122. // stloc V(isinst T(testedOperand))
  123. ILInstruction storeToV = block.Instructions[pos];
  124. if (!storeToV.MatchStLoc(out var v, out var value))
  125. return false;
  126. if (value.MatchLdLoc(s))
  127. {
  128. // stloc v(ldloc s)
  129. pos--;
  130. if (pos < 0 || !block.Instructions[pos].MatchStLoc(s, out value))
  131. return false;
  132. if (v.Kind is not (VariableKind.Local or VariableKind.StackSlot))
  133. return false;
  134. if (s.LoadCount != 2)
  135. return false;
  136. }
  137. else
  138. {
  139. if (v != s)
  140. return false;
  141. }
  142. IType? unboxType;
  143. if (value is UnboxAny unboxAny)
  144. {
  145. // stloc S(unbox.any T(isinst T(testedOperand)))
  146. unboxType = unboxAny.Type;
  147. value = unboxAny.Argument;
  148. }
  149. else
  150. {
  151. unboxType = null;
  152. }
  153. if (value is not IsInst { Argument: var testedOperand, Type: var type })
  154. return false;
  155. if (type.IsReferenceType != true)
  156. return false;
  157. if (!(unboxType == null || type.Equals(unboxType)))
  158. return false;
  159. if (!v.Type.Equals(type))
  160. return false;
  161. if (!CheckAllUsesDominatedBy(v, container, trueInst, storeToV, loadInNullCheck, context, ref cfg))
  162. return false;
  163. context.Step($"Type pattern matching {v.Name}", block);
  164. // if (match.type[T].notnull(V = testedOperand)) br trueBlock
  165. var ifInst = (IfInstruction)block.Instructions.SecondToLastOrDefault()!;
  166. ifInst.Condition = new MatchInstruction(v, testedOperand) {
  167. CheckNotNull = true,
  168. CheckType = true
  169. }.WithILRange(ifInst.Condition);
  170. ifInst.TrueInst = trueInst;
  171. block.Instructions[block.Instructions.Count - 1] = falseInst;
  172. block.Instructions.RemoveRange(pos, ifInst.ChildIndex - pos);
  173. v.Kind = VariableKind.PatternLocal;
  174. DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, trueInst, falseInst, container, context, ref cfg);
  175. return true;
  176. }
  177. private static ILInstruction DetectPropertySubPatterns(MatchInstruction parentPattern, ILInstruction trueInst,
  178. ILInstruction parentFalseInst, BlockContainer container, ILTransformContext context, ref ControlFlowGraph? cfg)
  179. {
  180. if (!context.Settings.RecursivePatternMatching)
  181. {
  182. return trueInst;
  183. }
  184. while (true)
  185. {
  186. Block? trueBlock = trueInst as Block;
  187. if (!(trueBlock != null || trueInst.MatchBranch(out trueBlock)))
  188. {
  189. break;
  190. }
  191. if (!(trueBlock.IncomingEdgeCount == 1 && trueBlock.Parent == container))
  192. {
  193. break;
  194. }
  195. var nextTrueInst = DetectPropertySubPattern(parentPattern, trueBlock, parentFalseInst, context, ref cfg);
  196. if (nextTrueInst != null)
  197. {
  198. trueInst = nextTrueInst;
  199. }
  200. else
  201. {
  202. break;
  203. }
  204. }
  205. return trueInst;
  206. }
  207. private static ILInstruction? DetectPropertySubPattern(MatchInstruction parentPattern, Block block,
  208. ILInstruction parentFalseInst, ILTransformContext context, ref ControlFlowGraph? cfg)
  209. {
  210. // if (match.notnull.type[System.String] (V_0 = callvirt get_C(ldloc V_2))) br IL_0022
  211. // br IL_0037
  212. if (MatchBlockContainingOneCondition(block, out var condition, out var trueInst, out var falseInst))
  213. {
  214. bool negate = false;
  215. if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, falseInst))
  216. {
  217. if (!DetectExitPoints.CompatibleExitInstruction(parentFalseInst, trueInst))
  218. {
  219. return null;
  220. }
  221. ExtensionMethods.Swap(ref trueInst, ref falseInst);
  222. negate = true;
  223. }
  224. if (MatchInstruction.IsPatternMatch(condition, out var operand, context.Settings))
  225. {
  226. if (!PropertyOrFieldAccess(operand, out var target, out _))
  227. {
  228. return null;
  229. }
  230. if (!target.MatchLdLocRef(parentPattern.Variable))
  231. {
  232. return null;
  233. }
  234. if (negate && !context.Settings.PatternCombinators)
  235. {
  236. return null;
  237. }
  238. context.Step("Move property sub pattern", condition);
  239. if (negate)
  240. {
  241. condition = Comp.LogicNot(condition);
  242. }
  243. parentPattern.SubPatterns.Add(condition);
  244. }
  245. else if (PropertyOrFieldAccess(condition, out var target, out _))
  246. {
  247. if (!target.MatchLdLocRef(parentPattern.Variable))
  248. {
  249. return null;
  250. }
  251. if (!negate && !context.Settings.PatternCombinators)
  252. {
  253. return null;
  254. }
  255. context.Step("Sub pattern: implicit != 0", condition);
  256. parentPattern.SubPatterns.Add(new Comp(negate ? ComparisonKind.Equality : ComparisonKind.Inequality,
  257. Sign.None, condition, new LdcI4(0)));
  258. }
  259. else
  260. {
  261. return null;
  262. }
  263. block.Instructions.Clear();
  264. block.Instructions.Add(trueInst);
  265. return trueInst;
  266. }
  267. else if (block.Instructions[0].MatchStLoc(out var targetVariable, out var operand))
  268. {
  269. if (!PropertyOrFieldAccess(operand, out var target, out var member))
  270. {
  271. return null;
  272. }
  273. if (!target.MatchLdLocRef(parentPattern.Variable))
  274. {
  275. return null;
  276. }
  277. if (!targetVariable.Type.Equals(member.ReturnType))
  278. {
  279. return null;
  280. }
  281. if (!CheckAllUsesDominatedBy(targetVariable, (BlockContainer)block.Parent!, block, block.Instructions[0], null, context, ref cfg))
  282. {
  283. return null;
  284. }
  285. context.Step("Property var pattern", block);
  286. var varPattern = new MatchInstruction(targetVariable, operand)
  287. .WithILRange(block.Instructions[0]);
  288. parentPattern.SubPatterns.Add(varPattern);
  289. block.Instructions.RemoveAt(0);
  290. targetVariable.Kind = VariableKind.PatternLocal;
  291. if (targetVariable.Type.IsKnownType(KnownTypeCode.NullableOfT))
  292. {
  293. return MatchNullableHasValueCheckPattern(block, varPattern, parentFalseInst, context, ref cfg)
  294. ?? block;
  295. }
  296. var instructionAfterNullCheck = MatchNullCheckPattern(block, varPattern, parentFalseInst, context);
  297. if (instructionAfterNullCheck != null)
  298. {
  299. return DetectPropertySubPatterns(varPattern, instructionAfterNullCheck, parentFalseInst, (BlockContainer)block.Parent!, context, ref cfg);
  300. }
  301. else if (targetVariable.Type.IsReferenceType == false)
  302. {
  303. return DetectPropertySubPatterns(varPattern, block, parentFalseInst, (BlockContainer)block.Parent!, context, ref cfg);
  304. }
  305. else
  306. {
  307. return block;
  308. }
  309. }
  310. else
  311. {
  312. return null;
  313. }
  314. }
  315. private static ILInstruction? MatchNullCheckPattern(Block block, MatchInstruction varPattern,
  316. ILInstruction parentFalseInst, ILTransformContext context)
  317. {
  318. if (!MatchBlockContainingOneCondition(block, out var condition, out var trueInst, out var falseInst))
  319. {
  320. return null;
  321. }
  322. if (condition.MatchCompEqualsNull(out var arg) && arg.MatchLdLoc(varPattern.Variable))
  323. {
  324. ExtensionMethods.Swap(ref trueInst, ref falseInst);
  325. }
  326. else if (condition.MatchCompNotEqualsNull(out arg) && arg.MatchLdLoc(varPattern.Variable))
  327. {
  328. }
  329. else
  330. {
  331. return null;
  332. }
  333. if (!DetectExitPoints.CompatibleExitInstruction(falseInst, parentFalseInst))
  334. {
  335. return null;
  336. }
  337. context.Step("Null check pattern", block);
  338. varPattern.CheckNotNull = true;
  339. block.Instructions.Clear();
  340. block.Instructions.Add(trueInst);
  341. return trueInst;
  342. }
  343. private static ILInstruction? MatchNullableHasValueCheckPattern(Block block, MatchInstruction varPattern,
  344. ILInstruction parentFalseInst, ILTransformContext context, ref ControlFlowGraph? cfg)
  345. {
  346. if (!(varPattern.Variable.StoreCount == 1 && varPattern.Variable.LoadCount == 0))
  347. {
  348. return null;
  349. }
  350. if (!MatchBlockContainingOneCondition(block, out var condition, out var trueInst, out var falseInst))
  351. {
  352. return null;
  353. }
  354. if (!NullableLiftingTransform.MatchHasValueCall(condition, varPattern.Variable))
  355. {
  356. return null;
  357. }
  358. if (!DetectExitPoints.CompatibleExitInstruction(falseInst, parentFalseInst))
  359. {
  360. if (DetectExitPoints.CompatibleExitInstruction(trueInst, parentFalseInst))
  361. {
  362. if (!(varPattern.Variable.AddressCount == 1))
  363. {
  364. return null;
  365. }
  366. context.Step("Nullable.HasValue check -> null pattern", block);
  367. varPattern.ReplaceWith(new Comp(ComparisonKind.Equality, ComparisonLiftingKind.CSharp, StackType.O, Sign.None, varPattern.TestedOperand, new LdNull()));
  368. block.Instructions.Clear();
  369. block.Instructions.Add(falseInst);
  370. return falseInst;
  371. }
  372. return null;
  373. }
  374. if (varPattern.Variable.AddressCount == 1 && context.Settings.PatternCombinators)
  375. {
  376. context.Step("Nullable.HasValue check -> not null pattern", block);
  377. varPattern.ReplaceWith(new Comp(ComparisonKind.Inequality, ComparisonLiftingKind.CSharp, StackType.O, Sign.None, varPattern.TestedOperand, new LdNull()));
  378. block.Instructions.Clear();
  379. block.Instructions.Add(trueInst);
  380. return trueInst;
  381. }
  382. else if (varPattern.Variable.AddressCount != 2)
  383. {
  384. return null;
  385. }
  386. if (!(trueInst.MatchBranch(out var trueBlock) && trueBlock.Parent == block.Parent && trueBlock.IncomingEdgeCount == 1))
  387. {
  388. return null;
  389. }
  390. if (trueBlock.Instructions[0].MatchStLoc(out var newTargetVariable, out var getValueOrDefaultCall)
  391. && NullableLiftingTransform.MatchGetValueOrDefault(getValueOrDefaultCall, varPattern.Variable))
  392. {
  393. context.Step("Nullable.HasValue check + Nullable.GetValueOrDefault pattern", block);
  394. varPattern.CheckNotNull = true;
  395. varPattern.Variable = newTargetVariable;
  396. newTargetVariable.Kind = VariableKind.PatternLocal;
  397. block.Instructions.Clear();
  398. block.Instructions.Add(trueInst);
  399. trueBlock.Instructions.RemoveAt(0);
  400. return DetectPropertySubPatterns(varPattern, trueBlock, parentFalseInst, (BlockContainer)block.Parent!, context, ref cfg);
  401. }
  402. else if (MatchBlockContainingOneCondition(trueBlock, out condition, out trueInst, out falseInst))
  403. {
  404. if (!(condition is Comp comp
  405. && MatchInstruction.IsConstant(comp.Right)
  406. && NullableLiftingTransform.MatchGetValueOrDefault(comp.Left, varPattern.Variable)))
  407. {
  408. return null;
  409. }
  410. if (!(context.Settings.RelationalPatterns || comp.Kind is ComparisonKind.Equality or ComparisonKind.Inequality))
  411. {
  412. return null;
  413. }
  414. bool negated = false;
  415. if (!DetectExitPoints.CompatibleExitInstruction(falseInst, parentFalseInst))
  416. {
  417. if (!DetectExitPoints.CompatibleExitInstruction(trueInst, parentFalseInst))
  418. {
  419. return null;
  420. }
  421. ExtensionMethods.Swap(ref trueInst, ref falseInst);
  422. negated = true;
  423. }
  424. if (comp.Kind == (negated ? ComparisonKind.Equality : ComparisonKind.Inequality))
  425. {
  426. return null;
  427. }
  428. if (negated && !context.Settings.PatternCombinators)
  429. {
  430. return null;
  431. }
  432. context.Step("Nullable.HasValue check + Nullable.GetValueOrDefault pattern", block);
  433. // varPattern: match (v = testedOperand)
  434. // comp: comp.i4(call GetValueOrDefault(ldloca v) != ldc.i4 42)
  435. // =>
  436. // comp.i4.lifted(testedOperand != ldc.i4 42)
  437. block.Instructions.Clear();
  438. block.Instructions.Add(trueInst);
  439. trueBlock.Instructions.Clear();
  440. comp.Left = varPattern.TestedOperand;
  441. comp.LiftingKind = ComparisonLiftingKind.CSharp;
  442. if (negated)
  443. {
  444. comp = Comp.LogicNot(comp);
  445. }
  446. varPattern.ReplaceWith(comp);
  447. return trueInst;
  448. }
  449. else
  450. {
  451. return null;
  452. }
  453. }
  454. private static bool PropertyOrFieldAccess(ILInstruction operand, [NotNullWhen(true)] out ILInstruction? target, [NotNullWhen(true)] out IMember? member)
  455. {
  456. if (operand is CallInstruction {
  457. Method: {
  458. SymbolKind: SymbolKind.Accessor,
  459. AccessorKind: MethodSemanticsAttributes.Getter,
  460. AccessorOwner: { } _member
  461. },
  462. Arguments: [var _target]
  463. })
  464. {
  465. target = _target;
  466. member = _member;
  467. return true;
  468. }
  469. else if (operand.MatchLdFld(out target, out var field))
  470. {
  471. member = field;
  472. return true;
  473. }
  474. else
  475. {
  476. member = null;
  477. return false;
  478. }
  479. }
  480. private static bool MatchBlockContainingOneCondition(Block block, [NotNullWhen(true)] out ILInstruction? condition, [NotNullWhen(true)] out ILInstruction? trueInst, [NotNullWhen(true)] out ILInstruction? falseInst)
  481. {
  482. switch (block.Instructions.Count)
  483. {
  484. case 2:
  485. return block.MatchIfAtEndOfBlock(out condition, out trueInst, out falseInst);
  486. case 3:
  487. condition = null;
  488. if (!block.MatchIfAtEndOfBlock(out var loadTemp, out trueInst, out falseInst))
  489. return false;
  490. if (!(loadTemp.MatchLdLoc(out var tempVar) && tempVar.IsSingleDefinition && tempVar.LoadCount == 1))
  491. return false;
  492. if (!block.Instructions[0].MatchStLoc(tempVar, out condition))
  493. return false;
  494. while (condition.MatchLogicNot(out var arg))
  495. {
  496. condition = arg;
  497. ExtensionMethods.Swap(ref trueInst, ref falseInst);
  498. }
  499. return true;
  500. default:
  501. condition = null;
  502. trueInst = null;
  503. falseInst = null;
  504. return false;
  505. }
  506. }
  507. private static bool CheckAllUsesDominatedBy(ILVariable v, BlockContainer container, ILInstruction trueInst,
  508. ILInstruction storeToV, ILInstruction? loadInNullCheck, ILTransformContext context, ref ControlFlowGraph? cfg)
  509. {
  510. var targetBlock = trueInst as Block;
  511. if (targetBlock == null && !trueInst.MatchBranch(out targetBlock))
  512. {
  513. return false;
  514. }
  515. if (targetBlock.Parent != container)
  516. return false;
  517. if (targetBlock.IncomingEdgeCount != 1)
  518. return false;
  519. cfg ??= new ControlFlowGraph(container, context.CancellationToken);
  520. var targetBlockNode = cfg.GetNode(targetBlock);
  521. var uses = v.LoadInstructions.Concat<ILInstruction>(v.AddressInstructions)
  522. .Concat(v.StoreInstructions.Cast<ILInstruction>());
  523. foreach (var use in uses)
  524. {
  525. if (use == storeToV || use == loadInNullCheck)
  526. continue;
  527. Block? found = null;
  528. for (ILInstruction? current = use; current != null; current = current.Parent)
  529. {
  530. if (current.Parent == container)
  531. {
  532. found = (Block)current;
  533. break;
  534. }
  535. }
  536. if (found == null)
  537. return false;
  538. var node = cfg.GetNode(found);
  539. if (!targetBlockNode.Dominates(node))
  540. return false;
  541. }
  542. return true;
  543. }
  544. /// Block {
  545. /// ...
  546. /// [stloc temp(ldloc testedOperand)]
  547. /// if (comp.o(isinst T(ldloc testedOperand) == ldnull)) br falseBlock
  548. /// br unboxBlock
  549. /// }
  550. ///
  551. /// Block unboxBlock (incoming: 1) {
  552. /// stloc V(unbox.any T(ldloc temp))
  553. /// ...
  554. /// }
  555. /// =>
  556. /// Block {
  557. /// ...
  558. /// if (match.type[T].notnull(V = testedOperand)) br unboxBlock
  559. /// br falseBlock
  560. /// }
  561. private bool PatternMatchValueTypes(Block block, BlockContainer container, ILTransformContext context, ref ControlFlowGraph? cfg)
  562. {
  563. if (!MatchIsInstBlock(block, out var type, out var testedOperand, out var testedVariable,
  564. out var boxType1, out var unboxBlock, out var falseInst))
  565. {
  566. return false;
  567. }
  568. StLoc? tempStore = block.Instructions.ElementAtOrDefault(block.Instructions.Count - 3) as StLoc;
  569. if (tempStore == null || !tempStore.Value.MatchLdLoc(testedVariable))
  570. {
  571. tempStore = null;
  572. }
  573. if (!MatchUnboxBlock(unboxBlock, type, out var unboxOperand, out var boxType2, out var storeToV))
  574. {
  575. return false;
  576. }
  577. if (!object.Equals(boxType1, boxType2))
  578. {
  579. return false;
  580. }
  581. if (unboxOperand == testedVariable)
  582. {
  583. // do nothing
  584. }
  585. else if (unboxOperand == tempStore?.Variable)
  586. {
  587. if (!(tempStore.Variable.IsSingleDefinition && tempStore.Variable.LoadCount == 1))
  588. return false;
  589. }
  590. else
  591. {
  592. return false;
  593. }
  594. if (!CheckAllUsesDominatedBy(storeToV.Variable, container, unboxBlock, storeToV, null, context, ref cfg))
  595. return false;
  596. context.Step($"PatternMatching with {storeToV.Variable.Name}", block);
  597. var ifInst = (IfInstruction)block.Instructions.SecondToLastOrDefault()!;
  598. ifInst.Condition = new MatchInstruction(storeToV.Variable, testedOperand) {
  599. CheckNotNull = true,
  600. CheckType = true
  601. };
  602. ifInst.TrueInst = new Branch(unboxBlock);
  603. block.Instructions[^1] = falseInst;
  604. unboxBlock.Instructions.RemoveAt(0);
  605. if (unboxOperand == tempStore?.Variable)
  606. {
  607. block.Instructions.Remove(tempStore);
  608. }
  609. // HACK: condition detection uses StartILOffset of blocks to decide which branch of if-else
  610. // should become the then-branch. Change the unboxBlock StartILOffset from an offset inside
  611. // the pattern matching machinery to an offset belonging to an instruction in the then-block.
  612. unboxBlock.SetILRange(unboxBlock.Instructions[0]);
  613. storeToV.Variable.Kind = VariableKind.PatternLocal;
  614. DetectPropertySubPatterns((MatchInstruction)ifInst.Condition, unboxBlock, falseInst, container, context, ref cfg);
  615. return true;
  616. }
  617. /// ...
  618. /// if (comp.o(isinst T(ldloc testedOperand) == ldnull)) br falseBlock
  619. /// br unboxBlock
  620. /// - or -
  621. /// ...
  622. /// if (comp.o(isinst T(box ``0(ldloc testedOperand)) == ldnull)) br falseBlock
  623. /// br unboxBlock
  624. private bool MatchIsInstBlock(Block block,
  625. [NotNullWhen(true)] out IType? type,
  626. [NotNullWhen(true)] out ILInstruction? testedOperand,
  627. [NotNullWhen(true)] out ILVariable? testedVariable,
  628. out IType? boxType,
  629. [NotNullWhen(true)] out Block? unboxBlock,
  630. [NotNullWhen(true)] out ILInstruction? falseInst)
  631. {
  632. type = null;
  633. testedOperand = null;
  634. testedVariable = null;
  635. boxType = null;
  636. unboxBlock = null;
  637. if (!block.MatchIfAtEndOfBlock(out var condition, out var trueInst, out falseInst))
  638. {
  639. return false;
  640. }
  641. if (condition.MatchCompEqualsNull(out var arg))
  642. {
  643. ExtensionMethods.Swap(ref trueInst, ref falseInst);
  644. }
  645. else if (condition.MatchCompNotEqualsNull(out arg))
  646. {
  647. // do nothing
  648. }
  649. else
  650. {
  651. return false;
  652. }
  653. if (!arg.MatchIsInst(out testedOperand, out type))
  654. {
  655. return false;
  656. }
  657. if (!(testedOperand.MatchBox(out var boxArg, out boxType) && boxType.Kind == TypeKind.TypeParameter))
  658. {
  659. boxArg = testedOperand;
  660. }
  661. if (!boxArg.MatchLdLoc(out testedVariable))
  662. {
  663. return false;
  664. }
  665. return trueInst.MatchBranch(out unboxBlock) && unboxBlock.Parent == block.Parent;
  666. }
  667. /// Block unboxBlock (incoming: 1) {
  668. /// stloc V(unbox.any T(ldloc testedOperand))
  669. /// ...
  670. /// - or -
  671. /// stloc V(unbox.any T(isinst T(box ``0(ldloc testedOperand))))
  672. /// ...
  673. /// }
  674. private bool MatchUnboxBlock(Block unboxBlock, IType type, [NotNullWhen(true)] out ILVariable? testedVariable,
  675. out IType? boxType, [NotNullWhen(true)] out StLoc? storeToV)
  676. {
  677. boxType = null;
  678. storeToV = null;
  679. testedVariable = null;
  680. if (unboxBlock.IncomingEdgeCount != 1)
  681. return false;
  682. storeToV = unboxBlock.Instructions[0] as StLoc;
  683. if (storeToV == null)
  684. return false;
  685. var value = storeToV.Value;
  686. if (!(value.MatchUnboxAny(out var arg, out var t) && t.Equals(type)))
  687. return false;
  688. if (arg.MatchIsInst(out var isinstArg, out var isinstType) && isinstType.Equals(type))
  689. {
  690. arg = isinstArg;
  691. }
  692. if (arg.MatchBox(out var boxArg, out boxType) && boxType.Kind == TypeKind.TypeParameter)
  693. {
  694. arg = boxArg;
  695. }
  696. if (!arg.MatchLdLoc(out testedVariable))
  697. {
  698. return false;
  699. }
  700. if (boxType != null && !boxType.Equals(testedVariable.Type))
  701. {
  702. return false;
  703. }
  704. return true;
  705. }
  706. }
  707. }