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.

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