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.

782 lines
29 KiB

  1. // Copyright (c) 2020 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.Diagnostics;
  19. using System.Linq;
  20. using ICSharpCode.Decompiler.TypeSystem;
  21. using ICSharpCode.Decompiler.TypeSystem.Implementation;
  22. namespace ICSharpCode.Decompiler.IL.Transforms
  23. {
  24. /// <summary>
  25. /// Transform for the C# 8 System.Index / System.Range feature
  26. /// </summary>
  27. class IndexRangeTransform : IStatementTransform
  28. {
  29. /// <summary>
  30. /// Called by expression transforms.
  31. /// Handles the `array[System.Index]` cases.
  32. /// </summary>
  33. public static bool HandleLdElema(LdElema ldelema, ILTransformContext context)
  34. {
  35. if (!context.Settings.Ranges)
  36. return false;
  37. if (!ldelema.Array.MatchLdLoc(out ILVariable array))
  38. return false;
  39. if (ldelema.Indices.Count != 1)
  40. return false; // the index/range feature doesn't support multi-dimensional arrays
  41. var index = ldelema.Indices[0];
  42. if (index is CallInstruction call && call.Method.Name == "GetOffset" && call.Method.DeclaringType.IsKnownType(KnownTypeCode.Index))
  43. {
  44. // ldelema T(ldloc array, call GetOffset(..., ldlen.i4(ldloc array)))
  45. // -> withsystemindex.ldelema T(ldloc array, ...)
  46. if (call.Arguments.Count != 2)
  47. return false;
  48. if (!(call.Arguments[1].MatchLdLen(StackType.I4, out var arrayLoad) && arrayLoad.MatchLdLoc(array)))
  49. return false;
  50. context.Step("ldelema with System.Index", ldelema);
  51. foreach (var node in call.Arguments[1].Descendants)
  52. ldelema.AddILRange(node);
  53. ldelema.AddILRange(call);
  54. ldelema.WithSystemIndex = true;
  55. // The method call had a `ref System.Index` argument for the this pointer, but we want a `System.Index` by-value.
  56. ldelema.Indices[0] = new LdObj(call.Arguments[0], call.Method.DeclaringType);
  57. return true;
  58. }
  59. else if (index is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub && !bni.IsLifted && !bni.CheckForOverflow)
  60. {
  61. // ldelema T(ldloc array, binary.sub.i4(ldlen.i4(ldloc array), ...))
  62. // -> withsystemindex.ldelema T(ldloc array, newobj System.Index(..., fromEnd: true))
  63. if (!(bni.Left.MatchLdLen(StackType.I4, out var arrayLoad) && arrayLoad.MatchLdLoc(array)))
  64. return false;
  65. var indexMethods = new IndexMethods(context.TypeSystem);
  66. if (!indexMethods.IsValid)
  67. return false; // don't use System.Index if not supported by the target framework
  68. context.Step("ldelema indexed from end", ldelema);
  69. foreach (var node in bni.Left.Descendants)
  70. ldelema.AddILRange(node);
  71. ldelema.AddILRange(bni);
  72. ldelema.WithSystemIndex = true;
  73. ldelema.Indices[0] = MakeIndex(IndexKind.FromEnd, bni.Right, indexMethods);
  74. return true;
  75. }
  76. return false;
  77. }
  78. class IndexMethods
  79. {
  80. public readonly IMethod IndexCtor;
  81. public readonly IMethod IndexImplicitConv;
  82. public readonly IMethod RangeCtor;
  83. public IType IndexType => IndexCtor?.DeclaringType;
  84. public IType RangeType => RangeCtor?.DeclaringType;
  85. public bool IsValid => IndexCtor != null && IndexImplicitConv != null && RangeCtor != null;
  86. public readonly IMethod RangeStartAt;
  87. public readonly IMethod RangeEndAt;
  88. public readonly IMethod RangeGetAll;
  89. public IndexMethods(ICompilation compilation)
  90. {
  91. var indexType = compilation.FindType(KnownTypeCode.Index);
  92. foreach (var ctor in indexType.GetConstructors(m => m.Parameters.Count == 2))
  93. {
  94. if (ctor.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32)
  95. && ctor.Parameters[1].Type.IsKnownType(KnownTypeCode.Boolean))
  96. {
  97. this.IndexCtor = ctor;
  98. }
  99. }
  100. foreach (var op in indexType.GetMethods(m => m.IsOperator && m.Name == "op_Implicit"))
  101. {
  102. if (op.Parameters.Count == 1 && op.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32))
  103. {
  104. this.IndexImplicitConv = op;
  105. }
  106. }
  107. var rangeType = compilation.FindType(KnownTypeCode.Range);
  108. foreach (var ctor in rangeType.GetConstructors(m => m.Parameters.Count == 2))
  109. {
  110. if (ctor.Parameters[0].Type.IsKnownType(KnownTypeCode.Index)
  111. && ctor.Parameters[1].Type.IsKnownType(KnownTypeCode.Index))
  112. {
  113. this.RangeCtor = ctor;
  114. }
  115. }
  116. foreach (var m in rangeType.GetMethods(m => m.Parameters.Count == 1))
  117. {
  118. if (m.Parameters.Count == 1 && m.Parameters[0].Type.IsKnownType(KnownTypeCode.Index))
  119. {
  120. if (m.Name == "StartAt")
  121. this.RangeStartAt = m;
  122. else if (m.Name == "EndAt")
  123. this.RangeEndAt = m;
  124. }
  125. }
  126. foreach (var p in rangeType.GetProperties(p => p.IsStatic && p.Name == "All"))
  127. {
  128. this.RangeGetAll = p.Getter;
  129. }
  130. }
  131. public static bool IsRangeCtor(IMethod method)
  132. {
  133. return method.SymbolKind == SymbolKind.Constructor
  134. && method.Parameters.Count == 2
  135. && method.DeclaringType.IsKnownType(KnownTypeCode.Range)
  136. && method.Parameters.All(p => p.Type.IsKnownType(KnownTypeCode.Index));
  137. }
  138. }
  139. void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
  140. {
  141. if (!context.Settings.Ranges)
  142. return;
  143. int startPos = pos;
  144. ILVariable containerVar = null;
  145. // The container length access may be a separate instruction, or it may be inline with the variable's use
  146. if (MatchContainerLengthStore(block.Instructions[pos], out ILVariable containerLengthVar, ref containerVar))
  147. {
  148. // stloc containerLengthVar(call get_Length/get_Count(ldloc container))
  149. pos++;
  150. }
  151. else
  152. {
  153. // Reset if MatchContainerLengthStore only had a partial match. MatchGetOffset() will then set `containerVar`.
  154. containerLengthVar = null;
  155. containerVar = null;
  156. }
  157. if (block.Instructions[pos].MatchStLoc(out var rangeVar, out var rangeVarInit) && rangeVar.Type.IsKnownType(KnownTypeCode.Range))
  158. {
  159. // stloc rangeVar(rangeVarInit)
  160. pos++;
  161. }
  162. else
  163. {
  164. rangeVar = null;
  165. rangeVarInit = null;
  166. }
  167. // stloc startOffsetVar(call GetOffset(startIndexLoad, ldloc length))
  168. if (!(block.Instructions[pos].MatchStLoc(out ILVariable startOffsetVar, out ILInstruction startOffsetVarInit)
  169. && startOffsetVar.IsSingleDefinition && startOffsetVar.StackType == StackType.I4))
  170. {
  171. // Not our primary indexing/slicing pattern.
  172. // However, we might be dealing with a partially-transformed pattern that needs to be extended.
  173. ExtendSlicing();
  174. return;
  175. }
  176. var startIndexKind = MatchGetOffset(startOffsetVarInit, out ILInstruction startIndexLoad, containerLengthVar, ref containerVar);
  177. pos++;
  178. if (startOffsetVar.LoadCount == 1)
  179. {
  180. TransformIndexing();
  181. }
  182. else if (startOffsetVar.LoadCount == 2)
  183. {
  184. // might be slicing: startOffset is used once for the slice length calculation, and once for the Slice() method call
  185. TransformSlicing();
  186. }
  187. void TransformIndexing()
  188. {
  189. // complex_expr(call get_Item(ldloc container, ldloc startOffsetVar))
  190. if (rangeVar != null)
  191. return;
  192. if (!(startOffsetVar.LoadInstructions.Single().Parent is CallInstruction call))
  193. return;
  194. if (call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Getter && call.Arguments.Count == 2)
  195. {
  196. if (call.Method.AccessorOwner?.SymbolKind != SymbolKind.Indexer)
  197. return;
  198. if (call.Method.Parameters.Count != 1)
  199. return;
  200. }
  201. else if (call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Setter && call.Arguments.Count == 3)
  202. {
  203. if (call.Method.AccessorOwner?.SymbolKind != SymbolKind.Indexer)
  204. return;
  205. if (call.Method.Parameters.Count != 2)
  206. return;
  207. }
  208. else if (IsSlicingMethod(call.Method))
  209. {
  210. TransformSlicing(sliceLengthWasMisdetectedAsStartOffset: true);
  211. return;
  212. }
  213. else
  214. {
  215. return;
  216. }
  217. if (startIndexKind == IndexKind.FromStart)
  218. {
  219. // FromStart is only relevant for slicing; indexing from the start does not involve System.Index at all.
  220. return;
  221. }
  222. if (!CheckContainerLengthVariableUseCount(containerLengthVar, startIndexKind))
  223. {
  224. return;
  225. }
  226. if (!call.IsDescendantOf(block.Instructions[pos]))
  227. return;
  228. // startOffsetVar might be used deep inside a complex statement, ensure we can inline up to that point:
  229. for (int i = startPos; i < pos; i++)
  230. {
  231. if (!ILInlining.CanInlineInto(block.Instructions[pos], startOffsetVar, block.Instructions[i]))
  232. return;
  233. }
  234. if (!call.Method.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32))
  235. return;
  236. if (!MatchContainerVar(call.Arguments[0], ref containerVar))
  237. return;
  238. if (!call.Arguments[1].MatchLdLoc(startOffsetVar))
  239. return;
  240. var specialMethods = new IndexMethods(context.TypeSystem);
  241. if (!specialMethods.IsValid)
  242. return;
  243. if (!CSharpWillGenerateIndexer(call.Method.DeclaringType, slicing: false))
  244. return;
  245. context.Step($"{call.Method.Name} indexed with {startIndexKind}", call);
  246. var newMethod = new SyntheticRangeIndexAccessor(call.Method, specialMethods.IndexType, slicing: false);
  247. var newCall = CallInstruction.Create(call.OpCode, newMethod);
  248. newCall.ConstrainedTo = call.ConstrainedTo;
  249. newCall.ILStackWasEmpty = call.ILStackWasEmpty;
  250. newCall.Arguments.Add(call.Arguments[0]);
  251. newCall.Arguments.Add(MakeIndex(startIndexKind, startIndexLoad, specialMethods));
  252. newCall.Arguments.AddRange(call.Arguments.Skip(2));
  253. newCall.AddILRange(call);
  254. for (int i = startPos; i < pos; i++)
  255. {
  256. newCall.AddILRange(block.Instructions[i]);
  257. }
  258. call.ReplaceWith(newCall);
  259. block.Instructions.RemoveRange(startPos, pos - startPos);
  260. }
  261. void TransformSlicing(bool sliceLengthWasMisdetectedAsStartOffset = false)
  262. {
  263. ILVariable sliceLengthVar;
  264. ILInstruction sliceLengthVarInit;
  265. if (sliceLengthWasMisdetectedAsStartOffset)
  266. {
  267. // Special case: when slicing without a start point, the slice length calculation is mis-detected as the start offset,
  268. // and since it only has a single use, we end in TransformIndexing(), which then calls TransformSlicing
  269. // on this code path.
  270. sliceLengthVar = startOffsetVar;
  271. sliceLengthVarInit = ((StLoc)sliceLengthVar.StoreInstructions.Single()).Value;
  272. startOffsetVar = null;
  273. startIndexLoad = new LdcI4(0);
  274. startIndexKind = IndexKind.TheStart;
  275. }
  276. else
  277. {
  278. // stloc containerLengthVar(call get_Length(ldloc containerVar))
  279. // stloc startOffset(call GetOffset(startIndexLoad, ldloc length))
  280. // -- we are here --
  281. // stloc sliceLengthVar(binary.sub.i4(call GetOffset(endIndexLoad, ldloc length), ldloc startOffset))
  282. // complex_expr(call Slice(ldloc containerVar, ldloc startOffset, ldloc sliceLength))
  283. if (!block.Instructions[pos].MatchStLoc(out sliceLengthVar, out sliceLengthVarInit))
  284. return;
  285. pos++;
  286. }
  287. if (!(sliceLengthVar.IsSingleDefinition && sliceLengthVar.LoadCount == 1))
  288. return;
  289. if (!MatchSliceLength(sliceLengthVarInit, out IndexKind endIndexKind, out ILInstruction endIndexLoad, containerLengthVar, ref containerVar, startOffsetVar))
  290. return;
  291. if (!CheckContainerLengthVariableUseCount(containerLengthVar, startIndexKind, endIndexKind))
  292. {
  293. return;
  294. }
  295. if (rangeVar != null)
  296. {
  297. return; // this should only ever happen in the second step (ExtendSlicing)
  298. }
  299. if (!(sliceLengthVar.LoadInstructions.Single().Parent is CallInstruction call))
  300. return;
  301. if (!call.IsDescendantOf(block.Instructions[pos]))
  302. return;
  303. if (!IsSlicingMethod(call.Method))
  304. return;
  305. if (call.Arguments.Count != 3)
  306. return;
  307. if (!MatchContainerVar(call.Arguments[0], ref containerVar))
  308. return;
  309. if (startOffsetVar == null)
  310. {
  311. Debug.Assert(startIndexKind == IndexKind.TheStart);
  312. if (!call.Arguments[1].MatchLdcI4(0))
  313. return;
  314. }
  315. else
  316. {
  317. if (!call.Arguments[1].MatchLdLoc(startOffsetVar))
  318. return;
  319. if (!ILInlining.CanMoveInto(startOffsetVarInit, block.Instructions[pos], call.Arguments[1]))
  320. return;
  321. }
  322. if (!call.Arguments[2].MatchLdLoc(sliceLengthVar))
  323. return;
  324. if (!ILInlining.CanMoveInto(sliceLengthVarInit, block.Instructions[pos], call.Arguments[2]))
  325. return;
  326. if (!CSharpWillGenerateIndexer(call.Method.DeclaringType, slicing: true))
  327. return;
  328. var specialMethods = new IndexMethods(context.TypeSystem);
  329. if (!specialMethods.IsValid)
  330. return;
  331. context.Step($"{call.Method.Name} sliced with {startIndexKind}..{endIndexKind}", call);
  332. var newMethod = new SyntheticRangeIndexAccessor(call.Method, specialMethods.RangeType, slicing: true);
  333. var newCall = CallInstruction.Create(call.OpCode, newMethod);
  334. newCall.ConstrainedTo = call.ConstrainedTo;
  335. newCall.ILStackWasEmpty = call.ILStackWasEmpty;
  336. newCall.Arguments.Add(call.Arguments[0]);
  337. newCall.Arguments.Add(MakeRange(startIndexKind, startIndexLoad, endIndexKind, endIndexLoad, specialMethods));
  338. newCall.AddILRange(call);
  339. for (int i = startPos; i < pos; i++)
  340. {
  341. newCall.AddILRange(block.Instructions[i]);
  342. }
  343. call.ReplaceWith(newCall);
  344. block.Instructions.RemoveRange(startPos, pos - startPos);
  345. }
  346. ILInstruction MakeRange(IndexKind startIndexKind, ILInstruction startIndexLoad, IndexKind endIndexKind, ILInstruction endIndexLoad, IndexMethods specialMethods)
  347. {
  348. if (rangeVar != null)
  349. {
  350. return rangeVarInit;
  351. }
  352. else if (startIndexKind == IndexKind.TheStart && endIndexKind == IndexKind.TheEnd && specialMethods.RangeGetAll != null)
  353. {
  354. return new Call(specialMethods.RangeGetAll);
  355. }
  356. else if (startIndexKind == IndexKind.TheStart && specialMethods.RangeEndAt != null)
  357. {
  358. var rangeCtorCall = new Call(specialMethods.RangeEndAt);
  359. rangeCtorCall.Arguments.Add(MakeIndex(endIndexKind, endIndexLoad, specialMethods));
  360. return rangeCtorCall;
  361. }
  362. else if (endIndexKind == IndexKind.TheEnd && specialMethods.RangeStartAt != null)
  363. {
  364. var rangeCtorCall = new Call(specialMethods.RangeStartAt);
  365. rangeCtorCall.Arguments.Add(MakeIndex(startIndexKind, startIndexLoad, specialMethods));
  366. return rangeCtorCall;
  367. }
  368. else
  369. {
  370. var rangeCtorCall = new NewObj(specialMethods.RangeCtor);
  371. rangeCtorCall.Arguments.Add(MakeIndex(startIndexKind, startIndexLoad, specialMethods));
  372. rangeCtorCall.Arguments.Add(MakeIndex(endIndexKind, endIndexLoad, specialMethods));
  373. return rangeCtorCall;
  374. }
  375. }
  376. void ExtendSlicing()
  377. {
  378. // We might be dealing with a situation where we executed TransformSlicing() in a previous run of this transform
  379. // that only looked at a part of the instructions making up the slicing pattern.
  380. // The first run would have mis-detected slicing from end as slicing from start.
  381. // This results in code like:
  382. // int length = span.Length;
  383. // Console.WriteLine(span[GetIndex(1).GetOffset(length)..GetIndex(2).GetOffset(length)].ToString());
  384. // or:
  385. // int length = span.Length;
  386. // Range range = GetRange();
  387. // Console.WriteLine(span[range.Start.GetOffset(length)..range.End.GetOffset(length)].ToString());
  388. if (containerLengthVar == null)
  389. {
  390. return; // need a container length to extend with
  391. }
  392. Debug.Assert(containerLengthVar.IsSingleDefinition);
  393. Debug.Assert(containerLengthVar.LoadCount == 1 || containerLengthVar.LoadCount == 2);
  394. NewObj rangeCtorCall = null;
  395. foreach (var inst in containerLengthVar.LoadInstructions[0].Ancestors)
  396. {
  397. if (inst is NewObj newobj && IndexMethods.IsRangeCtor(newobj.Method))
  398. {
  399. rangeCtorCall = newobj;
  400. break;
  401. }
  402. if (inst == block)
  403. break;
  404. }
  405. if (rangeCtorCall == null)
  406. return;
  407. // Now match the pattern that TransformSlicing() generated in the IndexKind.FromStart case
  408. if (!(rangeCtorCall.Parent is CallInstruction { Method: SyntheticRangeIndexAccessor _ } slicingCall))
  409. return;
  410. if (!MatchContainerVar(slicingCall.Arguments[0], ref containerVar))
  411. return;
  412. if (!slicingCall.IsDescendantOf(block.Instructions[pos]))
  413. return;
  414. Debug.Assert(rangeCtorCall.Arguments.Count == 2);
  415. if (!MatchIndexImplicitConv(rangeCtorCall.Arguments[0], out var startOffsetInst))
  416. return;
  417. if (!MatchIndexImplicitConv(rangeCtorCall.Arguments[1], out var endOffsetInst))
  418. return;
  419. var startIndexKind = MatchGetOffset(startOffsetInst, out var startIndexLoad, containerLengthVar, ref containerVar);
  420. var endIndexKind = MatchGetOffset(endOffsetInst, out var endIndexLoad, containerLengthVar, ref containerVar);
  421. if (!CheckContainerLengthVariableUseCount(containerLengthVar, startIndexKind, endIndexKind))
  422. {
  423. return;
  424. }
  425. // holds because we've used containerLengthVar at least once
  426. Debug.Assert(startIndexKind != IndexKind.FromStart || endIndexKind != IndexKind.FromStart);
  427. if (rangeVar != null)
  428. {
  429. if (!ILInlining.CanMoveInto(rangeVarInit, block.Instructions[pos], startIndexLoad))
  430. return;
  431. if (!MatchIndexFromRange(startIndexKind, startIndexLoad, rangeVar, "get_Start"))
  432. return;
  433. if (!MatchIndexFromRange(endIndexKind, endIndexLoad, rangeVar, "get_End"))
  434. return;
  435. }
  436. var specialMethods = new IndexMethods(context.TypeSystem);
  437. if (!specialMethods.IsValid)
  438. return;
  439. context.Step("Merge containerLengthVar into slicing", slicingCall);
  440. rangeCtorCall.ReplaceWith(MakeRange(startIndexKind, startIndexLoad, endIndexKind, endIndexLoad, specialMethods));
  441. for (int i = startPos; i < pos; i++)
  442. {
  443. slicingCall.AddILRange(block.Instructions[i]);
  444. }
  445. block.Instructions.RemoveRange(startPos, pos - startPos);
  446. }
  447. }
  448. private bool MatchIndexImplicitConv(ILInstruction inst, out ILInstruction offsetInst)
  449. {
  450. offsetInst = null;
  451. if (!(inst is CallInstruction call))
  452. return false;
  453. if (!(call.Method.IsOperator && call.Method.Name == "op_Implicit"))
  454. return false;
  455. var op = call.Method;
  456. if (!(op.Parameters.Count == 1 && op.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32)))
  457. return false;
  458. if (!op.DeclaringType.IsKnownType(KnownTypeCode.Index))
  459. return false;
  460. offsetInst = call.Arguments.Single();
  461. return true;
  462. }
  463. static bool IsSlicingMethod(IMethod method)
  464. {
  465. if (method.IsExtensionMethod)
  466. return false;
  467. if (method.Parameters.Count != 2)
  468. return false;
  469. if (!method.Parameters.All(p => p.Type.IsKnownType(KnownTypeCode.Int32)))
  470. return false;
  471. return method.Name == "Slice"
  472. || (method.Name == "Substring" && method.DeclaringType.IsKnownType(KnownTypeCode.String));
  473. }
  474. /// <summary>
  475. /// Check that the number of uses of the containerLengthVar variable matches those expected in the pattern.
  476. /// </summary>
  477. private bool CheckContainerLengthVariableUseCount(ILVariable containerLengthVar, IndexKind startIndexKind, IndexKind endIndexKind = IndexKind.FromStart)
  478. {
  479. int expectedUses = 0;
  480. if (startIndexKind != IndexKind.FromStart && startIndexKind != IndexKind.TheStart)
  481. expectedUses += 1;
  482. if (endIndexKind != IndexKind.FromStart && endIndexKind != IndexKind.TheStart)
  483. expectedUses += 1;
  484. if (containerLengthVar != null)
  485. {
  486. return containerLengthVar.LoadCount == expectedUses;
  487. }
  488. else
  489. {
  490. return expectedUses <= 1; // can have one inline use
  491. }
  492. }
  493. /// <summary>
  494. /// Matches 'addressof System.Index(call get_Start/get_End(ldloca rangeVar))'
  495. /// </summary>
  496. static bool MatchIndexFromRange(IndexKind indexKind, ILInstruction indexLoad, ILVariable rangeVar, string accessorName)
  497. {
  498. if (indexKind != IndexKind.RefSystemIndex)
  499. return false;
  500. if (!(indexLoad is AddressOf addressOf))
  501. return false;
  502. if (!addressOf.Type.IsKnownType(KnownTypeCode.Index))
  503. return false;
  504. if (!(addressOf.Value is Call call))
  505. return false;
  506. if (call.Method.Name != accessorName)
  507. return false;
  508. if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.Range))
  509. return false;
  510. if (call.Arguments.Count != 1)
  511. return false;
  512. return call.Arguments[0].MatchLdLoca(rangeVar);
  513. }
  514. static ILInstruction MakeIndex(IndexKind indexKind, ILInstruction indexLoad, IndexMethods specialMethods)
  515. {
  516. if (indexKind == IndexKind.RefSystemIndex)
  517. {
  518. // stloc containerLengthVar(call get_Length/get_Count(ldloc container))
  519. // stloc startOffsetVar(call GetOffset(startIndexLoad, ldloc length))
  520. // complex_expr(call get_Item(ldloc container, ldloc startOffsetVar))
  521. // -->
  522. // complex_expr(call get_Item(ldloc container, ldobj startIndexLoad))
  523. return new LdObj(indexLoad, specialMethods.IndexType);
  524. }
  525. else if (indexKind == IndexKind.FromEnd || indexKind == IndexKind.TheEnd)
  526. {
  527. // stloc offsetVar(binary.sub.i4(call get_Length/get_Count(ldloc container), startIndexLoad))
  528. // complex_expr(call get_Item(ldloc container, ldloc startOffsetVar))
  529. // -->
  530. // complex_expr(call get_Item(ldloc container, newobj System.Index(startIndexLoad, fromEnd: true)))
  531. return new NewObj(specialMethods.IndexCtor) { Arguments = { indexLoad, new LdcI4(1) } };
  532. }
  533. else
  534. {
  535. Debug.Assert(indexKind == IndexKind.FromStart || indexKind == IndexKind.TheStart);
  536. return new Call(specialMethods.IndexImplicitConv) { Arguments = { indexLoad } };
  537. }
  538. }
  539. /// <summary>
  540. /// Gets whether the C# compiler will call `container[int]` when using `container[Index]`.
  541. /// </summary>
  542. private bool CSharpWillGenerateIndexer(IType declaringType, bool slicing)
  543. {
  544. bool foundInt32Overload = false;
  545. bool foundIndexOverload = false;
  546. bool foundRangeOverload = false;
  547. bool foundCountProperty = false;
  548. foreach (var prop in declaringType.GetProperties(p => p.IsIndexer || (p.Name == "Length" || p.Name == "Count")))
  549. {
  550. if (prop.IsIndexer && prop.Parameters.Count == 1)
  551. {
  552. var p = prop.Parameters[0];
  553. if (p.Type.IsKnownType(KnownTypeCode.Int32))
  554. {
  555. foundInt32Overload = true;
  556. }
  557. else if (p.Type.IsKnownType(KnownTypeCode.Index))
  558. {
  559. foundIndexOverload = true;
  560. }
  561. else if (p.Type.IsKnownType(KnownTypeCode.Range))
  562. {
  563. foundRangeOverload = true;
  564. }
  565. }
  566. else if (prop.Name == "Length" || prop.Name == "Count")
  567. {
  568. foundCountProperty = true;
  569. }
  570. }
  571. if (slicing)
  572. {
  573. return /* foundSlicingMethod && */ foundCountProperty && !foundRangeOverload;
  574. }
  575. else
  576. {
  577. return foundInt32Overload && foundCountProperty && !foundIndexOverload;
  578. }
  579. }
  580. /// <summary>
  581. /// Matches the instruction:
  582. /// stloc containerLengthVar(call get_Length/get_Count(ldloc containerVar))
  583. /// </summary>
  584. static bool MatchContainerLengthStore(ILInstruction inst, out ILVariable lengthVar, ref ILVariable containerVar)
  585. {
  586. if (!inst.MatchStLoc(out lengthVar, out var init))
  587. return false;
  588. if (!(lengthVar.IsSingleDefinition && lengthVar.StackType == StackType.I4))
  589. return false;
  590. if (lengthVar.LoadCount == 0 || lengthVar.LoadCount > 2)
  591. return false;
  592. return MatchContainerLength(init, null, ref containerVar);
  593. }
  594. /// <summary>
  595. /// If lengthVar is non-null, matches 'ldloc lengthVar'.
  596. ///
  597. /// Otherwise, matches the instruction:
  598. /// call get_Length/get_Count(ldloc containerVar)
  599. /// </summary>
  600. static bool MatchContainerLength(ILInstruction init, ILVariable lengthVar, ref ILVariable containerVar)
  601. {
  602. if (lengthVar != null)
  603. {
  604. Debug.Assert(containerVar != null);
  605. return init.MatchLdLoc(lengthVar);
  606. }
  607. if (!(init is CallInstruction call))
  608. return false;
  609. if (call.ResultType != StackType.I4)
  610. return false;
  611. if (!(call.Method.IsAccessor && call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Getter))
  612. return false;
  613. if (!(call.Method.AccessorOwner is IProperty lengthProp))
  614. return false;
  615. if (lengthProp.Name == "Length")
  616. {
  617. // OK, Length is preferred
  618. }
  619. else if (lengthProp.Name == "Count")
  620. {
  621. // Also works, but only if the type doesn't have "Length"
  622. if (lengthProp.DeclaringType.GetProperties(p => p.Name == "Length").Any())
  623. return false;
  624. }
  625. if (!lengthProp.ReturnType.IsKnownType(KnownTypeCode.Int32))
  626. return false;
  627. if (lengthProp.IsVirtual && call.OpCode != OpCode.CallVirt)
  628. return false;
  629. if (call.Arguments.Count != 1)
  630. return false;
  631. return MatchContainerVar(call.Arguments[0], ref containerVar);
  632. }
  633. static bool MatchContainerVar(ILInstruction inst, ref ILVariable containerVar)
  634. {
  635. if (containerVar != null)
  636. {
  637. return inst.MatchLdLoc(containerVar) || inst.MatchLdLoca(containerVar);
  638. }
  639. else
  640. {
  641. return inst.MatchLdLoc(out containerVar) || inst.MatchLdLoca(out containerVar);
  642. }
  643. }
  644. enum IndexKind
  645. {
  646. /// <summary>
  647. /// indexLoad is an integer, from the start of the container
  648. /// </summary>
  649. FromStart,
  650. /// <summary>
  651. /// indexLoad is loading the address of a System.Index struct
  652. /// </summary>
  653. RefSystemIndex,
  654. /// <summary>
  655. /// indexLoad is an integer, from the end of the container
  656. /// </summary>
  657. FromEnd,
  658. /// <summary>
  659. /// Always equivalent to `0`, used for the start-index when slicing without a startpoint `a[..end]`
  660. /// </summary>
  661. TheStart,
  662. /// <summary>
  663. /// Always equivalent to `^0`, used for the end-index when slicing without an endpoint `a[start..]`
  664. /// </summary>
  665. TheEnd,
  666. }
  667. /// <summary>
  668. /// Matches an instruction computing an offset:
  669. /// call System.Index.GetOffset(indexLoad, ldloc containerLengthVar)
  670. /// or
  671. /// binary.sub.i4(ldloc containerLengthVar, indexLoad)
  672. ///
  673. /// Anything else not matching these patterns is interpreted as an `int` expression from the start of the container.
  674. /// </summary>
  675. static IndexKind MatchGetOffset(ILInstruction inst, out ILInstruction indexLoad,
  676. ILVariable containerLengthVar, ref ILVariable containerVar)
  677. {
  678. indexLoad = inst;
  679. if (MatchContainerLength(inst, containerLengthVar, ref containerVar))
  680. {
  681. indexLoad = new LdcI4(0);
  682. return IndexKind.TheEnd;
  683. }
  684. else if (inst is CallInstruction call)
  685. {
  686. // call System.Index.GetOffset(indexLoad, ldloc containerLengthVar)
  687. if (call.Method.Name != "GetOffset")
  688. return IndexKind.FromStart;
  689. if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.Index))
  690. return IndexKind.FromStart;
  691. if (call.Arguments.Count != 2)
  692. return IndexKind.FromStart;
  693. if (!MatchContainerLength(call.Arguments[1], containerLengthVar, ref containerVar))
  694. return IndexKind.FromStart;
  695. indexLoad = call.Arguments[0];
  696. return IndexKind.RefSystemIndex;
  697. }
  698. else if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub)
  699. {
  700. if (bni.CheckForOverflow || bni.ResultType != StackType.I4 || bni.IsLifted)
  701. return IndexKind.FromStart;
  702. // binary.sub.i4(ldloc containerLengthVar, indexLoad)
  703. if (!MatchContainerLength(bni.Left, containerLengthVar, ref containerVar))
  704. return IndexKind.FromStart;
  705. indexLoad = bni.Right;
  706. return IndexKind.FromEnd;
  707. }
  708. else
  709. {
  710. return IndexKind.FromStart;
  711. }
  712. }
  713. /// <summary>
  714. /// Matches an instruction computing a slice length:
  715. /// binary.sub.i4(call GetOffset(endIndexLoad, ldloc length), ldloc startOffset))
  716. /// </summary>
  717. static bool MatchSliceLength(ILInstruction inst, out IndexKind endIndexKind, out ILInstruction endIndexLoad, ILVariable containerLengthVar, ref ILVariable containerVar, ILVariable startOffsetVar)
  718. {
  719. endIndexKind = default;
  720. endIndexLoad = default;
  721. if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub)
  722. {
  723. if (bni.CheckForOverflow || bni.ResultType != StackType.I4 || bni.IsLifted)
  724. return false;
  725. if (startOffsetVar == null)
  726. {
  727. // When slicing without explicit start point: `a[..endIndex]`
  728. if (!bni.Right.MatchLdcI4(0))
  729. return false;
  730. }
  731. else
  732. {
  733. if (!bni.Right.MatchLdLoc(startOffsetVar))
  734. return false;
  735. }
  736. endIndexKind = MatchGetOffset(bni.Left, out endIndexLoad, containerLengthVar, ref containerVar);
  737. return true;
  738. }
  739. else if (startOffsetVar == null)
  740. {
  741. // When slicing without explicit start point: `a[..endIndex]`, the compiler doesn't always emit the "- 0".
  742. endIndexKind = MatchGetOffset(inst, out endIndexLoad, containerLengthVar, ref containerVar);
  743. return true;
  744. }
  745. else
  746. {
  747. return false;
  748. }
  749. }
  750. }
  751. }