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.

866 lines
33 KiB

4 years ago
8 years ago
  1. // Copyright (c) 2015 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. using System;
  19. using System.Collections.Generic;
  20. using System.Linq;
  21. using System.Reflection.Metadata;
  22. using ICSharpCode.Decompiler.TypeSystem;
  23. using ICSharpCode.Decompiler.Util;
  24. namespace ICSharpCode.Decompiler.IL.Transforms
  25. {
  26. /// <summary>
  27. /// Transforms array initialization pattern of System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray.
  28. /// For collection and object initializers see <see cref="TransformCollectionAndObjectInitializers"/>
  29. /// </summary>
  30. public class TransformArrayInitializers : IStatementTransform
  31. {
  32. StatementTransformContext context;
  33. void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
  34. {
  35. if (!context.Settings.ArrayInitializers)
  36. return;
  37. this.context = context;
  38. try
  39. {
  40. if (DoTransform(context.Function, block, pos))
  41. return;
  42. if (DoTransformMultiDim(context.Function, block, pos))
  43. return;
  44. if (context.Settings.StackAllocInitializers && DoTransformStackAllocInitializer(block, pos))
  45. return;
  46. if (DoTransformInlineRuntimeHelpersInitializeArray(block, pos))
  47. return;
  48. }
  49. finally
  50. {
  51. this.context = null;
  52. }
  53. }
  54. bool DoTransform(ILFunction function, Block body, int pos)
  55. {
  56. if (pos >= body.Instructions.Count - 2)
  57. return false;
  58. ILInstruction inst = body.Instructions[pos];
  59. if (inst.MatchStLoc(out var v, out var newarrExpr) && MatchNewArr(newarrExpr, out var elementType, out var arrayLength))
  60. {
  61. if (HandleRuntimeHelpersInitializeArray(body, pos + 1, v, elementType, arrayLength, out var values, out var initArrayPos))
  62. {
  63. context.Step("HandleRuntimeHelperInitializeArray: single-dim", inst);
  64. var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
  65. var block = BlockFromInitializer(tempStore, elementType, arrayLength, values);
  66. body.Instructions[pos] = new StLoc(v, block);
  67. body.Instructions.RemoveAt(initArrayPos);
  68. ILInlining.InlineIfPossible(body, pos, context);
  69. return true;
  70. }
  71. if (arrayLength.Length == 1)
  72. {
  73. if (HandleSimpleArrayInitializer(function, body, pos + 1, v, arrayLength, out var arrayValues, out var instructionsToRemove))
  74. {
  75. context.Step("HandleSimpleArrayInitializer: single-dim", inst);
  76. var block = new Block(BlockKind.ArrayInitializer);
  77. var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
  78. block.Instructions.Add(new StLoc(tempStore, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray())));
  79. block.Instructions.AddRange(arrayValues.Select(
  80. t => {
  81. var (indices, value) = t;
  82. if (value == null)
  83. value = GetNullExpression(elementType);
  84. return StElem(new LdLoc(tempStore), indices, value, elementType);
  85. }
  86. ));
  87. block.FinalInstruction = new LdLoc(tempStore);
  88. body.Instructions[pos] = new StLoc(v, block);
  89. body.Instructions.RemoveRange(pos + 1, instructionsToRemove);
  90. ILInlining.InlineIfPossible(body, pos, context);
  91. return true;
  92. }
  93. if (HandleJaggedArrayInitializer(body, pos + 1, v, elementType, arrayLength[0], out ILVariable finalStore, out values, out instructionsToRemove))
  94. {
  95. context.Step("HandleJaggedArrayInitializer: single-dim", inst);
  96. var block = new Block(BlockKind.ArrayInitializer);
  97. var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
  98. block.Instructions.Add(new StLoc(tempStore, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray())));
  99. block.Instructions.AddRange(values.SelectWithIndex((i, value) => StElem(new LdLoc(tempStore), new[] { new LdcI4(i) }, value, elementType)));
  100. block.FinalInstruction = new LdLoc(tempStore);
  101. body.Instructions[pos] = new StLoc(finalStore, block);
  102. body.Instructions.RemoveRange(pos + 1, instructionsToRemove);
  103. ILInlining.InlineIfPossible(body, pos, context);
  104. return true;
  105. }
  106. }
  107. }
  108. return false;
  109. }
  110. internal static bool TransformSpanTArrayInitialization(NewObj inst, StatementTransformContext context, out Block block)
  111. {
  112. block = null;
  113. if (!context.Settings.ArrayInitializers)
  114. return false;
  115. if (MatchSpanTCtorWithPointerAndSize(inst, context, out var elementType, out var field, out var size))
  116. {
  117. if (field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
  118. {
  119. var valuesList = new List<ILInstruction>();
  120. var initialValue = field.GetInitialValue(context.PEFile.Reader, context.TypeSystem);
  121. if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList))
  122. {
  123. var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType));
  124. block = BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray());
  125. return true;
  126. }
  127. }
  128. }
  129. return false;
  130. }
  131. static bool MatchSpanTCtorWithPointerAndSize(NewObj newObj, StatementTransformContext context, out IType elementType, out FieldDefinition field, out int size)
  132. {
  133. field = default;
  134. size = default;
  135. elementType = null;
  136. IType type = newObj.Method.DeclaringType;
  137. if (!type.IsKnownType(KnownTypeCode.SpanOfT) && !type.IsKnownType(KnownTypeCode.ReadOnlySpanOfT))
  138. return false;
  139. if (newObj.Arguments.Count != 2 || type.TypeArguments.Count != 1)
  140. return false;
  141. elementType = type.TypeArguments[0];
  142. if (!newObj.Arguments[0].UnwrapConv(ConversionKind.StopGCTracking).MatchLdsFlda(out var member))
  143. return false;
  144. if (member.MetadataToken.IsNil)
  145. return false;
  146. if (!newObj.Arguments[1].MatchLdcI4(out size))
  147. return false;
  148. field = context.PEFile.Metadata.GetFieldDefinition((FieldDefinitionHandle)member.MetadataToken);
  149. return true;
  150. }
  151. bool DoTransformMultiDim(ILFunction function, Block body, int pos)
  152. {
  153. if (pos >= body.Instructions.Count - 2)
  154. return false;
  155. ILInstruction inst = body.Instructions[pos];
  156. if (inst.MatchStLoc(out var v, out var newarrExpr) && MatchNewArr(newarrExpr, out var elementType, out var length))
  157. {
  158. if (HandleRuntimeHelpersInitializeArray(body, pos + 1, v, elementType, length, out var values, out var initArrayPos))
  159. {
  160. context.Step("HandleRuntimeHelpersInitializeArray: multi-dim", inst);
  161. var block = BlockFromInitializer(v, elementType, length, values);
  162. body.Instructions[pos].ReplaceWith(new StLoc(v, block));
  163. body.Instructions.RemoveAt(initArrayPos);
  164. ILInlining.InlineIfPossible(body, pos, context);
  165. return true;
  166. }
  167. if (HandleSimpleArrayInitializer(function, body, pos + 1, v, length, out var arrayValues, out var instructionsToRemove))
  168. {
  169. context.Step("HandleSimpleArrayInitializer: multi-dim", inst);
  170. var block = new Block(BlockKind.ArrayInitializer);
  171. var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
  172. block.Instructions.Add(new StLoc(tempStore, new NewArr(elementType, length.Select(l => new LdcI4(l)).ToArray())));
  173. block.Instructions.AddRange(arrayValues.Select(
  174. t => {
  175. var (indices, value) = t;
  176. if (value == null)
  177. value = GetNullExpression(elementType);
  178. return StElem(new LdLoc(tempStore), indices, value, elementType);
  179. }
  180. ));
  181. block.FinalInstruction = new LdLoc(tempStore);
  182. body.Instructions[pos] = new StLoc(v, block);
  183. body.Instructions.RemoveRange(pos + 1, instructionsToRemove);
  184. ILInlining.InlineIfPossible(body, pos, context);
  185. return true;
  186. }
  187. }
  188. return false;
  189. }
  190. bool DoTransformStackAllocInitializer(Block body, int pos)
  191. {
  192. if (pos >= body.Instructions.Count - 2)
  193. return false;
  194. ILInstruction inst = body.Instructions[pos];
  195. if (inst.MatchStLoc(out var v, out var locallocExpr) && locallocExpr.MatchLocAlloc(out var lengthInst))
  196. {
  197. if (lengthInst.MatchLdcI(out var lengthInBytes) && HandleCpblkInitializer(body, pos + 1, v, lengthInBytes, out var blob, out var elementType))
  198. {
  199. context.Step("HandleCpblkInitializer", inst);
  200. var block = new Block(BlockKind.StackAllocInitializer);
  201. var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new PointerType(elementType));
  202. block.Instructions.Add(new StLoc(tempStore, locallocExpr));
  203. while (blob.RemainingBytes > 0)
  204. {
  205. block.Instructions.Add(StElemPtr(tempStore, blob.Offset, new LdcI4(blob.ReadByte()), elementType));
  206. }
  207. block.FinalInstruction = new LdLoc(tempStore);
  208. body.Instructions[pos] = new StLoc(v, block);
  209. body.Instructions.RemoveAt(pos + 1);
  210. ILInlining.InlineIfPossible(body, pos, context);
  211. ExpressionTransforms.RunOnSingleStatement(body.Instructions[pos], context);
  212. return true;
  213. }
  214. if (HandleSequentialLocAllocInitializer(body, pos + 1, v, locallocExpr, out elementType, out StObj[] values, out int instructionsToRemove))
  215. {
  216. context.Step("HandleSequentialLocAllocInitializer", inst);
  217. var block = new Block(BlockKind.StackAllocInitializer);
  218. var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new PointerType(elementType));
  219. block.Instructions.Add(new StLoc(tempStore, locallocExpr));
  220. block.Instructions.AddRange(values.Where(value => value != null).Select(value => RewrapStore(tempStore, value, elementType)));
  221. block.FinalInstruction = new LdLoc(tempStore);
  222. body.Instructions[pos] = new StLoc(v, block);
  223. body.Instructions.RemoveRange(pos + 1, instructionsToRemove);
  224. ILInlining.InlineIfPossible(body, pos, context);
  225. ExpressionTransforms.RunOnSingleStatement(body.Instructions[pos], context);
  226. return true;
  227. }
  228. }
  229. return false;
  230. }
  231. bool HandleCpblkInitializer(Block block, int pos, ILVariable v, long length, out BlobReader blob, out IType elementType)
  232. {
  233. blob = default;
  234. elementType = null;
  235. if (!block.Instructions[pos].MatchCpblk(out var dest, out var src, out var size))
  236. return false;
  237. if (!dest.MatchLdLoc(v) || !src.MatchLdsFlda(out var field) || !size.MatchLdcI4((int)length))
  238. return false;
  239. if (!(v.IsSingleDefinition && v.LoadCount == 2))
  240. return false;
  241. if (field.MetadataToken.IsNil)
  242. return false;
  243. if (!block.Instructions[pos + 1].MatchStLoc(out var finalStore, out var value))
  244. {
  245. var otherLoadOfV = v.LoadInstructions.FirstOrDefault(l => !(l.Parent is Cpblk));
  246. if (otherLoadOfV == null)
  247. return false;
  248. finalStore = otherLoadOfV.Parent.Extract(context);
  249. if (finalStore == null)
  250. return false;
  251. value = ((StLoc)finalStore.StoreInstructions[0]).Value;
  252. }
  253. var fd = context.PEFile.Metadata.GetFieldDefinition((FieldDefinitionHandle)field.MetadataToken);
  254. if (!fd.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
  255. return false;
  256. if (value.MatchLdLoc(v))
  257. {
  258. elementType = ((PointerType)finalStore.Type).ElementType;
  259. }
  260. else if (value is NewObj { Arguments: { Count: 2 } } newObj
  261. && newObj.Method.DeclaringType.IsKnownType(KnownTypeCode.SpanOfT)
  262. && newObj.Arguments[0].MatchLdLoc(v)
  263. && newObj.Arguments[1].MatchLdcI4((int)length))
  264. {
  265. elementType = ((ParameterizedType)newObj.Method.DeclaringType).TypeArguments[0];
  266. }
  267. else
  268. {
  269. return false;
  270. }
  271. blob = fd.GetInitialValue(context.PEFile.Reader, context.TypeSystem);
  272. return true;
  273. }
  274. bool HandleSequentialLocAllocInitializer(Block block, int pos, ILVariable store, ILInstruction locAllocInstruction, out IType elementType, out StObj[] values, out int instructionsToRemove)
  275. {
  276. int elementCount = 0;
  277. long minExpectedOffset = 0;
  278. values = null;
  279. elementType = null;
  280. instructionsToRemove = 0;
  281. if (!locAllocInstruction.MatchLocAlloc(out var lengthInstruction))
  282. return false;
  283. if (block.Instructions[pos].MatchInitblk(out var dest, out var value, out var size)
  284. && lengthInstruction.MatchLdcI(out long byteCount))
  285. {
  286. if (!dest.MatchLdLoc(store) || !size.MatchLdcI(byteCount))
  287. return false;
  288. instructionsToRemove++;
  289. pos++;
  290. }
  291. for (int i = pos; i < block.Instructions.Count; i++)
  292. {
  293. // match the basic stobj pattern
  294. if (!block.Instructions[i].MatchStObj(out ILInstruction target, out value, out var currentType)
  295. || value.Descendants.OfType<IInstructionWithVariableOperand>().Any(inst => inst.Variable == store))
  296. break;
  297. if (elementType != null && !currentType.Equals(elementType))
  298. break;
  299. elementType = currentType;
  300. // match the target
  301. // should be either ldloc store (at offset 0)
  302. // or binary.add(ldloc store, offset) where offset is either 'elementSize' or 'i * elementSize'
  303. if (!target.MatchLdLoc(store))
  304. {
  305. if (!target.MatchBinaryNumericInstruction(BinaryNumericOperator.Add, out var left, out var right))
  306. return false;
  307. if (!left.MatchLdLoc(store))
  308. break;
  309. var offsetInst = PointerArithmeticOffset.Detect(right, elementType, ((BinaryNumericInstruction)target).CheckForOverflow);
  310. if (offsetInst == null)
  311. return false;
  312. if (!offsetInst.MatchLdcI(out long offset) || offset < 0 || offset < minExpectedOffset)
  313. break;
  314. minExpectedOffset = offset;
  315. }
  316. if (values == null)
  317. {
  318. var countInstruction = PointerArithmeticOffset.Detect(lengthInstruction, elementType, checkForOverflow: true);
  319. if (countInstruction == null || !countInstruction.MatchLdcI(out long valuesLength) || valuesLength < 1)
  320. return false;
  321. values = new StObj[(int)valuesLength];
  322. }
  323. if (minExpectedOffset >= values.Length)
  324. break;
  325. values[minExpectedOffset] = (StObj)block.Instructions[i];
  326. elementCount++;
  327. }
  328. if (values == null || store.Kind != VariableKind.StackSlot || store.StoreCount != 1
  329. || store.AddressCount != 0 || store.LoadCount > values.Length + 1)
  330. return false;
  331. if (store.LoadInstructions.Last().Parent is StLoc finalStore)
  332. {
  333. elementType = ((PointerType)finalStore.Variable.Type).ElementType;
  334. }
  335. instructionsToRemove += elementCount;
  336. return elementCount <= values.Length;
  337. }
  338. ILInstruction RewrapStore(ILVariable target, StObj storeInstruction, IType type)
  339. {
  340. ILInstruction targetInst;
  341. if (storeInstruction.Target.MatchLdLoc(out _))
  342. targetInst = new LdLoc(target);
  343. else if (storeInstruction.Target.MatchBinaryNumericInstruction(BinaryNumericOperator.Add, out var left, out var right))
  344. {
  345. var old = (BinaryNumericInstruction)storeInstruction.Target;
  346. targetInst = new BinaryNumericInstruction(BinaryNumericOperator.Add, new LdLoc(target), right,
  347. old.CheckForOverflow, old.Sign);
  348. }
  349. else
  350. throw new NotSupportedException("This should never happen: Bug in HandleSequentialLocAllocInitializer!");
  351. return new StObj(targetInst, storeInstruction.Value, storeInstruction.Type);
  352. }
  353. ILInstruction StElemPtr(ILVariable target, int offset, LdcI4 value, IType type)
  354. {
  355. var targetInst = offset == 0 ? (ILInstruction)new LdLoc(target) : new BinaryNumericInstruction(
  356. BinaryNumericOperator.Add,
  357. new LdLoc(target),
  358. new Conv(new LdcI4(offset), PrimitiveType.I, false, Sign.Signed),
  359. false,
  360. Sign.None
  361. );
  362. return new StObj(targetInst, value, type);
  363. }
  364. /// <summary>
  365. /// Handle simple case where RuntimeHelpers.InitializeArray is not used.
  366. /// </summary>
  367. internal static bool HandleSimpleArrayInitializer(ILFunction function, Block block, int pos, ILVariable store, int[] arrayLength, out (ILInstruction[] Indices, ILInstruction Value)[] values, out int instructionsToRemove)
  368. {
  369. instructionsToRemove = 0;
  370. int elementCount = 0;
  371. int length = arrayLength.Aggregate(1, (t, l) => t * l);
  372. // Cannot pre-allocate the result array, because we do not know yet,
  373. // whether there is in fact an array initializer.
  374. // To prevent excessive allocations, use min(|block|, arraySize) als initial capacity.
  375. // This should prevent list-resizing as well as out-of-memory errors.
  376. values = null;
  377. var valuesList = new List<(ILInstruction[] Indices, ILInstruction Value)>(Math.Min(block.Instructions.Count, length));
  378. int[] nextMinimumIndex = new int[arrayLength.Length];
  379. ILInstruction[] CalculateNextIndices(InstructionCollection<ILInstruction> indices, out bool exactMatch)
  380. {
  381. var nextIndices = new ILInstruction[arrayLength.Length];
  382. exactMatch = true;
  383. if (indices == null)
  384. {
  385. for (int k = 0; k < nextIndices.Length; k++)
  386. {
  387. nextIndices[k] = new LdcI4(nextMinimumIndex[k]);
  388. }
  389. }
  390. else
  391. {
  392. bool previousComponentWasGreater = false;
  393. for (int k = 0; k < indices.Count; k++)
  394. {
  395. if (!indices[k].MatchLdcI4(out int index))
  396. return null;
  397. // index must be in range [0..length[ and must be greater than or equal to nextMinimumIndex
  398. // to avoid running out of bounds or accidentally reordering instructions or overwriting previous instructions.
  399. // However, leaving array slots empty is allowed, as those are filled with default values when the
  400. // initializer block is generated.
  401. if (index < 0 || index >= arrayLength[k] || (!previousComponentWasGreater && index < nextMinimumIndex[k]))
  402. return null;
  403. nextIndices[k] = new LdcI4(nextMinimumIndex[k]);
  404. if (index != nextMinimumIndex[k])
  405. {
  406. exactMatch = false;
  407. // this flag allows us to check whether the whole set of indices is smaller:
  408. // [3, 3] should be smaller than [4, 0] even though 3 > 0.
  409. if (index > nextMinimumIndex[k])
  410. previousComponentWasGreater = true;
  411. }
  412. }
  413. }
  414. for (int k = nextMinimumIndex.Length - 1; k >= 0; k--)
  415. {
  416. nextMinimumIndex[k]++;
  417. if (nextMinimumIndex[k] < arrayLength[k] || k == 0)
  418. break;
  419. nextMinimumIndex[k] = 0;
  420. }
  421. return nextIndices;
  422. }
  423. int i = pos;
  424. int step;
  425. while (i < block.Instructions.Count)
  426. {
  427. InstructionCollection<ILInstruction> indices;
  428. // stobj elementType(ldelema elementType(ldloc store, indices), value)
  429. if (block.Instructions[i].MatchStObj(out ILInstruction target, out ILInstruction value, out IType type))
  430. {
  431. if (!(target is LdElema ldelem && ldelem.Array.MatchLdLoc(store)))
  432. break;
  433. indices = ldelem.Indices;
  434. step = 1;
  435. // stloc s(ldelema elementType(ldloc store, indices))
  436. // stobj elementType(ldloc s, value)
  437. }
  438. else if (block.Instructions[i].MatchStLoc(out var addressTemporary, out var addressValue))
  439. {
  440. if (!(addressTemporary.IsSingleDefinition && addressTemporary.LoadCount == 1))
  441. break;
  442. if (!(addressValue is LdElema ldelem && ldelem.Array.MatchLdLoc(store)))
  443. break;
  444. if (!(i + 1 < block.Instructions.Count))
  445. break;
  446. if (!block.Instructions[i + 1].MatchStObj(out target, out value, out type))
  447. break;
  448. if (!(target.MatchLdLoc(addressTemporary) && ldelem.Array.MatchLdLoc(store)))
  449. break;
  450. indices = ldelem.Indices;
  451. step = 2;
  452. }
  453. else
  454. {
  455. break;
  456. }
  457. if (value.Descendants.OfType<IInstructionWithVariableOperand>().Any(inst => inst.Variable == store))
  458. break;
  459. if (indices.Count != arrayLength.Length)
  460. break;
  461. bool exact;
  462. if (length <= 0)
  463. break;
  464. do
  465. {
  466. var nextIndices = CalculateNextIndices(indices, out exact);
  467. if (nextIndices == null)
  468. return false;
  469. if (exact)
  470. {
  471. valuesList.Add((nextIndices, value));
  472. elementCount++;
  473. instructionsToRemove += step;
  474. }
  475. else
  476. {
  477. valuesList.Add((nextIndices, null));
  478. }
  479. } while (valuesList.Count < length && !exact);
  480. i += step;
  481. }
  482. if (i < block.Instructions.Count)
  483. {
  484. if (block.Instructions[i].MatchStObj(out ILInstruction target, out ILInstruction value, out IType type))
  485. {
  486. // An element of the array is modified directly after the initializer:
  487. // Abort transform, so that partial initializers are not constructed.
  488. if (target is LdElema ldelem && ldelem.Array.MatchLdLoc(store))
  489. return false;
  490. }
  491. }
  492. if (pos + instructionsToRemove >= block.Instructions.Count)
  493. return false;
  494. if (elementCount == 0)
  495. return false;
  496. bool mustTransform = ILInlining.IsCatchWhenBlock(block) || ILInlining.IsInConstructorInitializer(function, block.Instructions[pos]);
  497. // If there are not enough user-defined elements after scanning all instructions, we can abort the transform.
  498. // This avoids unnecessary allocations and OOM crashes (in case of int.MaxValue).
  499. if (elementCount < length / 3 - 5)
  500. {
  501. if (!mustTransform)
  502. return false;
  503. // .NET does not allow allocation of arrays >= 2 GB.
  504. if (length >= int.MaxValue)
  505. return false;
  506. }
  507. for (int j = valuesList.Count; j < length; j++)
  508. {
  509. var nextIndices = CalculateNextIndices(null, out _);
  510. if (nextIndices == null)
  511. return false;
  512. valuesList.Add((nextIndices, null));
  513. }
  514. values = valuesList.ToArray();
  515. return true;
  516. }
  517. bool HandleJaggedArrayInitializer(Block block, int pos, ILVariable store, IType elementType, int length, out ILVariable finalStore, out ILInstruction[] values, out int instructionsToRemove)
  518. {
  519. instructionsToRemove = 0;
  520. finalStore = null;
  521. // Cannot pre-allocate the result array, because we do not know yet,
  522. // whether there is in fact an array initializer.
  523. // To prevent excessive allocations, use min(|block|, arraySize) als initial capacity.
  524. // This should prevent list-resizing as well as out-of-memory errors.
  525. values = null;
  526. var valuesList = new List<ILInstruction>(Math.Min(block.Instructions.Count, length));
  527. for (int i = 0; i < length; i++)
  528. {
  529. // 1. Instruction: (optional) temporary copy of store
  530. bool hasTemporaryCopy = block.Instructions[pos].MatchStLoc(out var temp, out var storeLoad) && storeLoad.MatchLdLoc(store);
  531. ILInstruction initializer;
  532. if (hasTemporaryCopy)
  533. {
  534. if (!MatchJaggedArrayStore(block, pos + 1, temp, i, out initializer, out _))
  535. return false;
  536. }
  537. else
  538. {
  539. if (!MatchJaggedArrayStore(block, pos, store, i, out initializer, out _))
  540. return false;
  541. }
  542. valuesList.Add(initializer);
  543. int inc = hasTemporaryCopy ? 3 : 2;
  544. pos += inc;
  545. instructionsToRemove += inc;
  546. }
  547. // In case there is an extra copy of the store variable
  548. // Remove it and use its value instead.
  549. if (block.Instructions[pos].MatchStLoc(out finalStore, out var array))
  550. {
  551. if (!array.MatchLdLoc(store))
  552. return false;
  553. instructionsToRemove++;
  554. }
  555. else
  556. {
  557. finalStore = store;
  558. }
  559. values = valuesList.ToArray();
  560. return true;
  561. }
  562. bool MatchJaggedArrayStore(Block block, int pos, ILVariable store, int index, out ILInstruction initializer, out IType type)
  563. {
  564. initializer = null;
  565. type = null;
  566. // 3. Instruction: stobj(ldelema(ldloc temp, ldc.i4 0), ldloc tempArrayLoad)
  567. var finalInstruction = block.Instructions.ElementAtOrDefault(pos + 1);
  568. if (finalInstruction == null || !finalInstruction.MatchStObj(out var tempAccess, out var tempArrayLoad, out type)
  569. || !tempArrayLoad.MatchLdLoc(out var initializerStore))
  570. {
  571. return false;
  572. }
  573. if (!(tempAccess is LdElema elemLoad) || !elemLoad.Array.MatchLdLoc(store) || elemLoad.Indices.Count != 1
  574. || !elemLoad.Indices[0].MatchLdcI4(index))
  575. {
  576. return false;
  577. }
  578. // 2. Instruction: stloc(temp) with block (array initializer)
  579. var nextInstruction = block.Instructions.ElementAtOrDefault(pos);
  580. return nextInstruction != null
  581. && nextInstruction.MatchStLoc(initializerStore, out initializer)
  582. && initializer.OpCode == OpCode.Block;
  583. }
  584. static Block BlockFromInitializer(ILVariable v, IType elementType, int[] arrayLength, ILInstruction[] values)
  585. {
  586. var block = new Block(BlockKind.ArrayInitializer);
  587. block.Instructions.Add(new StLoc(v, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray())));
  588. int step = arrayLength.Length + 1;
  589. var indices = new List<ILInstruction>();
  590. for (int i = 0; i < values.Length / step; i++)
  591. {
  592. // values array is filled backwards
  593. var value = values[step * i];
  594. indices.EnsureCapacity(step - 1);
  595. for (int j = step - 1; j >= 1; j--)
  596. {
  597. indices.Add(values[step * i + j]);
  598. }
  599. block.Instructions.Add(StElem(new LdLoc(v), indices.ToArray(), value, elementType));
  600. indices.Clear();
  601. }
  602. block.FinalInstruction = new LdLoc(v);
  603. return block;
  604. }
  605. static bool MatchNewArr(ILInstruction instruction, out IType arrayType, out int[] length)
  606. {
  607. length = null;
  608. arrayType = null;
  609. if (!(instruction is NewArr newArr))
  610. return false;
  611. arrayType = newArr.Type;
  612. var args = newArr.Indices;
  613. length = new int[args.Count];
  614. for (int i = 0; i < args.Count; i++)
  615. {
  616. if (!args[i].MatchLdcI4(out int value) || value <= 0)
  617. return false;
  618. length[i] = value;
  619. }
  620. return true;
  621. }
  622. bool MatchInitializeArrayCall(ILInstruction instruction, out ILInstruction array, out FieldDefinition field)
  623. {
  624. array = null;
  625. field = default;
  626. if (!(instruction is Call call) || call.Arguments.Count != 2)
  627. return false;
  628. IMethod method = call.Method;
  629. if (!method.IsStatic || method.Name != "InitializeArray" || method.DeclaringTypeDefinition == null)
  630. return false;
  631. var declaringType = method.DeclaringTypeDefinition;
  632. if (declaringType.DeclaringType != null || declaringType.Name != "RuntimeHelpers"
  633. || declaringType.Namespace != "System.Runtime.CompilerServices")
  634. {
  635. return false;
  636. }
  637. array = call.Arguments[0];
  638. if (!call.Arguments[1].MatchLdMemberToken(out var member))
  639. return false;
  640. if (member.MetadataToken.IsNil)
  641. return false;
  642. field = context.PEFile.Metadata.GetFieldDefinition((FieldDefinitionHandle)member.MetadataToken);
  643. return true;
  644. }
  645. bool HandleRuntimeHelpersInitializeArray(Block body, int pos, ILVariable array, IType arrayType, int[] arrayLength, out ILInstruction[] values, out int foundPos)
  646. {
  647. if (MatchInitializeArrayCall(body.Instructions[pos], out var arrayInst, out var field) && arrayInst.MatchLdLoc(array))
  648. {
  649. if (field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
  650. {
  651. var valuesList = new List<ILInstruction>();
  652. var initialValue = field.GetInitialValue(context.PEFile.Reader, context.TypeSystem);
  653. if (DecodeArrayInitializer(arrayType, initialValue, arrayLength, valuesList))
  654. {
  655. values = valuesList.ToArray();
  656. foundPos = pos;
  657. return true;
  658. }
  659. }
  660. }
  661. values = null;
  662. foundPos = -1;
  663. return false;
  664. }
  665. /// <summary>
  666. /// call InitializeArray(newarr T(size), ldmembertoken fieldToken)
  667. /// =>
  668. /// Block (ArrayInitializer) {
  669. /// stloc i(newarr T(size))
  670. /// stobj T(ldelema T(... indices ...), value)
  671. /// final: ldloc i
  672. /// }
  673. /// </summary>
  674. bool DoTransformInlineRuntimeHelpersInitializeArray(Block body, int pos)
  675. {
  676. var inst = body.Instructions[pos];
  677. if (!MatchInitializeArrayCall(inst, out var arrayInst, out var field))
  678. return false;
  679. if (!MatchNewArr(arrayInst, out var elementType, out var arrayLength))
  680. return false;
  681. if (!field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
  682. return false;
  683. var valuesList = new List<ILInstruction>();
  684. var initialValue = field.GetInitialValue(context.PEFile.Reader, context.TypeSystem);
  685. if (!DecodeArrayInitializer(elementType, initialValue, arrayLength, valuesList))
  686. return false;
  687. context.Step("InlineRuntimeHelpersInitializeArray: single-dim", inst);
  688. var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType, arrayLength.Length));
  689. var block = BlockFromInitializer(tempStore, elementType, arrayLength, valuesList.ToArray());
  690. body.Instructions[pos] = block;
  691. ILInlining.InlineIfPossible(body, pos, context);
  692. return true;
  693. }
  694. static bool DecodeArrayInitializer(IType type, BlobReader initialValue, int[] arrayLength, List<ILInstruction> output)
  695. {
  696. TypeCode typeCode = ReflectionHelper.GetTypeCode(type);
  697. switch (typeCode)
  698. {
  699. case TypeCode.Boolean:
  700. case TypeCode.Byte:
  701. return DecodeArrayInitializer(initialValue, arrayLength, output, typeCode, (ref BlobReader r) => new LdcI4(r.ReadByte()));
  702. case TypeCode.SByte:
  703. return DecodeArrayInitializer(initialValue, arrayLength, output, typeCode, (ref BlobReader r) => new LdcI4(r.ReadSByte()));
  704. case TypeCode.Int16:
  705. return DecodeArrayInitializer(initialValue, arrayLength, output, typeCode, (ref BlobReader r) => new LdcI4(r.ReadInt16()));
  706. case TypeCode.Char:
  707. case TypeCode.UInt16:
  708. return DecodeArrayInitializer(initialValue, arrayLength, output, typeCode, (ref BlobReader r) => new LdcI4(r.ReadUInt16()));
  709. case TypeCode.Int32:
  710. case TypeCode.UInt32:
  711. return DecodeArrayInitializer(initialValue, arrayLength, output, typeCode, (ref BlobReader r) => new LdcI4(r.ReadInt32()));
  712. case TypeCode.Int64:
  713. case TypeCode.UInt64:
  714. return DecodeArrayInitializer(initialValue, arrayLength, output, typeCode, (ref BlobReader r) => new LdcI8(r.ReadInt64()));
  715. case TypeCode.Single:
  716. return DecodeArrayInitializer(initialValue, arrayLength, output, typeCode, (ref BlobReader r) => new LdcF4(r.ReadSingle()));
  717. case TypeCode.Double:
  718. return DecodeArrayInitializer(initialValue, arrayLength, output, typeCode, (ref BlobReader r) => new LdcF8(r.ReadDouble()));
  719. case TypeCode.Object:
  720. case TypeCode.Empty:
  721. var typeDef = type.GetDefinition();
  722. if (typeDef != null && typeDef.Kind == TypeKind.Enum)
  723. return DecodeArrayInitializer(typeDef.EnumUnderlyingType, initialValue, arrayLength, output);
  724. return false;
  725. default:
  726. return false;
  727. }
  728. }
  729. delegate ILInstruction ValueDecoder(ref BlobReader reader);
  730. static bool DecodeArrayInitializer(BlobReader initialValue, int[] arrayLength,
  731. List<ILInstruction> output, TypeCode elementType, ValueDecoder decoder)
  732. {
  733. int elementSize = ElementSizeOf(elementType);
  734. var totalLength = arrayLength.Aggregate(1, (t, l) => t * l);
  735. if (initialValue.RemainingBytes < (totalLength * elementSize))
  736. return false;
  737. output.EnsureCapacity(totalLength + totalLength * arrayLength.Length);
  738. for (int i = 0; i < totalLength; i++)
  739. {
  740. output.Add(decoder(ref initialValue));
  741. int next = i;
  742. for (int j = arrayLength.Length - 1; j >= 0; j--)
  743. {
  744. output.Add(new LdcI4(next % arrayLength[j]));
  745. next /= arrayLength[j];
  746. }
  747. }
  748. return true;
  749. }
  750. static ILInstruction StElem(ILInstruction array, ILInstruction[] indices, ILInstruction value, IType type)
  751. {
  752. if (type.GetStackType() != value.ResultType)
  753. {
  754. value = new Conv(value, type.ToPrimitiveType(), false, Sign.None);
  755. }
  756. return new StObj(new LdElema(type, array, indices) { DelayExceptions = true }, value, type);
  757. }
  758. internal static ILInstruction GetNullExpression(IType elementType)
  759. {
  760. ITypeDefinition typeDef = elementType.GetEnumUnderlyingType().GetDefinition();
  761. if (typeDef == null)
  762. return new DefaultValue(elementType);
  763. switch (typeDef.KnownTypeCode)
  764. {
  765. case KnownTypeCode.Boolean:
  766. case KnownTypeCode.Char:
  767. case KnownTypeCode.SByte:
  768. case KnownTypeCode.Byte:
  769. case KnownTypeCode.Int16:
  770. case KnownTypeCode.UInt16:
  771. case KnownTypeCode.Int32:
  772. case KnownTypeCode.UInt32:
  773. return new LdcI4(0);
  774. case KnownTypeCode.Int64:
  775. case KnownTypeCode.UInt64:
  776. return new LdcI8(0);
  777. case KnownTypeCode.Single:
  778. return new LdcF4(0);
  779. case KnownTypeCode.Double:
  780. return new LdcF8(0);
  781. case KnownTypeCode.Decimal:
  782. return new LdcDecimal(0);
  783. case KnownTypeCode.Void:
  784. throw new ArgumentException("void is not a valid element type!");
  785. case KnownTypeCode.IntPtr:
  786. case KnownTypeCode.UIntPtr:
  787. default:
  788. return new DefaultValue(elementType);
  789. }
  790. }
  791. static int ElementSizeOf(TypeCode elementType)
  792. {
  793. switch (elementType)
  794. {
  795. case TypeCode.Boolean:
  796. case TypeCode.Byte:
  797. case TypeCode.SByte:
  798. return 1;
  799. case TypeCode.Char:
  800. case TypeCode.Int16:
  801. case TypeCode.UInt16:
  802. return 2;
  803. case TypeCode.Int32:
  804. case TypeCode.UInt32:
  805. case TypeCode.Single:
  806. return 4;
  807. case TypeCode.Int64:
  808. case TypeCode.UInt64:
  809. case TypeCode.Double:
  810. return 8;
  811. default:
  812. throw new ArgumentOutOfRangeException(nameof(elementType));
  813. }
  814. }
  815. }
  816. }