Arm64Optimizer.cs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. using ARMeilleure.CodeGen.Optimizations;
  2. using ARMeilleure.IntermediateRepresentation;
  3. using ARMeilleure.Translation;
  4. using System.Collections.Generic;
  5. using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
  6. using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
  7. namespace ARMeilleure.CodeGen.Arm64
  8. {
  9. static class Arm64Optimizer
  10. {
  11. private const int MaxConstantUses = 10000;
  12. public static void RunPass(ControlFlowGraph cfg)
  13. {
  14. var constants = new Dictionary<ulong, Operand>();
  15. Operand GetConstantCopy(BasicBlock block, Operation operation, Operand source)
  16. {
  17. // If the constant has many uses, we also force a new constant mov to be added, in order
  18. // to avoid overflow of the counts field (that is limited to 16 bits).
  19. if (!constants.TryGetValue(source.Value, out var constant) || constant.UsesCount > MaxConstantUses)
  20. {
  21. constant = Local(source.Type);
  22. Operation copyOp = Operation(Instruction.Copy, constant, source);
  23. block.Operations.AddBefore(operation, copyOp);
  24. constants[source.Value] = constant;
  25. }
  26. return constant;
  27. }
  28. for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext)
  29. {
  30. constants.Clear();
  31. Operation nextNode;
  32. for (Operation node = block.Operations.First; node != default; node = nextNode)
  33. {
  34. nextNode = node.ListNext;
  35. // Insert copies for constants that can't fit on a 32-bit immediate.
  36. // Doing this early unblocks a few optimizations.
  37. if (node.Instruction == Instruction.Add)
  38. {
  39. Operand src1 = node.GetSource(0);
  40. Operand src2 = node.GetSource(1);
  41. if (src1.Kind == OperandKind.Constant && (src1.Relocatable || ConstTooLong(src1, OperandType.I32)))
  42. {
  43. node.SetSource(0, GetConstantCopy(block, node, src1));
  44. }
  45. if (src2.Kind == OperandKind.Constant && (src2.Relocatable || ConstTooLong(src2, OperandType.I32)))
  46. {
  47. node.SetSource(1, GetConstantCopy(block, node, src2));
  48. }
  49. }
  50. // Try to fold something like:
  51. // lsl x1, x1, #2
  52. // add x0, x0, x1
  53. // ldr x0, [x0]
  54. // add x2, x2, #16
  55. // ldr x2, [x2]
  56. // Into:
  57. // ldr x0, [x0, x1, lsl #2]
  58. // ldr x2, [x2, #16]
  59. if (IsMemoryLoadOrStore(node.Instruction))
  60. {
  61. OperandType type;
  62. if (node.Destination != default)
  63. {
  64. type = node.Destination.Type;
  65. }
  66. else
  67. {
  68. type = node.GetSource(1).Type;
  69. }
  70. Operand memOp = GetMemoryOperandOrNull(node.GetSource(0), type);
  71. if (memOp != default)
  72. {
  73. node.SetSource(0, memOp);
  74. }
  75. }
  76. }
  77. }
  78. Optimizer.RemoveUnusedNodes(cfg);
  79. }
  80. private static Operand GetMemoryOperandOrNull(Operand addr, OperandType type)
  81. {
  82. Operand baseOp = addr;
  83. // First we check if the address is the result of a local X with immediate
  84. // addition. If that is the case, then the baseOp is X, and the memory operand immediate
  85. // becomes the addition immediate. Otherwise baseOp keeps being the address.
  86. int imm = GetConstOp(ref baseOp, type);
  87. if (imm != 0)
  88. {
  89. return MemoryOp(type, baseOp, default, Multiplier.x1, imm);
  90. }
  91. // Now we check if the baseOp is the result of a local Y with a local Z addition.
  92. // If that is the case, we now set baseOp to Y and indexOp to Z. We further check
  93. // if Z is the result of a left shift of local W by a value == 0 or == Log2(AccessSize),
  94. // if that is the case, we set indexOp to W and adjust the scale value of the memory operand
  95. // to match that of the left shift.
  96. // There is one missed case, which is the address being a shift result, but this is
  97. // probably not worth optimizing as it should never happen.
  98. (Operand indexOp, Multiplier scale) = GetIndexOp(ref baseOp, type);
  99. // If baseOp is still equal to address, then there's nothing that can be optimized.
  100. if (baseOp == addr)
  101. {
  102. return default;
  103. }
  104. return MemoryOp(type, baseOp, indexOp, scale, 0);
  105. }
  106. private static int GetConstOp(ref Operand baseOp, OperandType accessType)
  107. {
  108. Operation operation = GetAsgOpWithInst(baseOp, Instruction.Add);
  109. if (operation == default)
  110. {
  111. return 0;
  112. }
  113. Operand src1 = operation.GetSource(0);
  114. Operand src2 = operation.GetSource(1);
  115. Operand constOp;
  116. Operand otherOp;
  117. if (src1.Kind == OperandKind.Constant && src2.Kind == OperandKind.LocalVariable)
  118. {
  119. constOp = src1;
  120. otherOp = src2;
  121. }
  122. else if (src1.Kind == OperandKind.LocalVariable && src2.Kind == OperandKind.Constant)
  123. {
  124. constOp = src2;
  125. otherOp = src1;
  126. }
  127. else
  128. {
  129. return 0;
  130. }
  131. // If we have addition by a constant that we can't encode on the instruction,
  132. // then we can't optimize it further.
  133. if (ConstTooLong(constOp, accessType))
  134. {
  135. return 0;
  136. }
  137. baseOp = otherOp;
  138. return constOp.AsInt32();
  139. }
  140. private static (Operand, Multiplier) GetIndexOp(ref Operand baseOp, OperandType accessType)
  141. {
  142. Operand indexOp = default;
  143. Multiplier scale = Multiplier.x1;
  144. Operation addOp = GetAsgOpWithInst(baseOp, Instruction.Add);
  145. if (addOp == default)
  146. {
  147. return (indexOp, scale);
  148. }
  149. Operand src1 = addOp.GetSource(0);
  150. Operand src2 = addOp.GetSource(1);
  151. if (src1.Kind != OperandKind.LocalVariable || src2.Kind != OperandKind.LocalVariable)
  152. {
  153. return (indexOp, scale);
  154. }
  155. baseOp = src1;
  156. indexOp = src2;
  157. Operation shlOp = GetAsgOpWithInst(src1, Instruction.ShiftLeft);
  158. bool indexOnSrc2 = false;
  159. if (shlOp == default)
  160. {
  161. shlOp = GetAsgOpWithInst(src2, Instruction.ShiftLeft);
  162. indexOnSrc2 = true;
  163. }
  164. if (shlOp != default)
  165. {
  166. Operand shSrc = shlOp.GetSource(0);
  167. Operand shift = shlOp.GetSource(1);
  168. int maxShift = Assembler.GetScaleForType(accessType);
  169. if (shSrc.Kind == OperandKind.LocalVariable &&
  170. shift.Kind == OperandKind.Constant &&
  171. (shift.Value == 0 || shift.Value == (ulong)maxShift))
  172. {
  173. scale = shift.Value switch
  174. {
  175. 1 => Multiplier.x2,
  176. 2 => Multiplier.x4,
  177. 3 => Multiplier.x8,
  178. 4 => Multiplier.x16,
  179. _ => Multiplier.x1
  180. };
  181. baseOp = indexOnSrc2 ? src1 : src2;
  182. indexOp = shSrc;
  183. }
  184. }
  185. return (indexOp, scale);
  186. }
  187. private static Operation GetAsgOpWithInst(Operand op, Instruction inst)
  188. {
  189. // If we have multiple assignments, folding is not safe
  190. // as the value may be different depending on the
  191. // control flow path.
  192. if (op.AssignmentsCount != 1)
  193. {
  194. return default;
  195. }
  196. Operation asgOp = op.Assignments[0];
  197. if (asgOp.Instruction != inst)
  198. {
  199. return default;
  200. }
  201. return asgOp;
  202. }
  203. private static bool IsMemoryLoadOrStore(Instruction inst)
  204. {
  205. return inst == Instruction.Load || inst == Instruction.Store;
  206. }
  207. private static bool ConstTooLong(Operand constOp, OperandType accessType)
  208. {
  209. if ((uint)constOp.Value != constOp.Value)
  210. {
  211. return true;
  212. }
  213. return !CodeGenCommon.ConstFitsOnUImm12(constOp.AsInt32(), accessType);
  214. }
  215. }
  216. }