1 //===---------- AArch64CollectLOH.cpp - AArch64 collect LOH pass --*- C++ -*-=// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 // 10 // This file contains a pass that collect the Linker Optimization Hint (LOH). 11 // This pass should be run at the very end of the compilation flow, just before 12 // assembly printer. 13 // To be useful for the linker, the LOH must be printed into the assembly file. 14 // 15 // A LOH describes a sequence of instructions that may be optimized by the 16 // linker. 17 // This same sequence cannot be optimized by the compiler because some of 18 // the information will be known at link time. 19 // For instance, consider the following sequence: 20 // L1: adrp xA, sym@PAGE 21 // L2: add xB, xA, sym@PAGEOFF 22 // L3: ldr xC, [xB, #imm] 23 // This sequence can be turned into: 24 // A literal load if sym@PAGE + sym@PAGEOFF + #imm - address(L3) is < 1MB: 25 // L3: ldr xC, sym+#imm 26 // It may also be turned into either the following more efficient 27 // code sequences: 28 // - If sym@PAGEOFF + #imm fits the encoding space of L3. 29 // L1: adrp xA, sym@PAGE 30 // L3: ldr xC, [xB, sym@PAGEOFF + #imm] 31 // - If sym@PAGE + sym@PAGEOFF - address(L1) < 1MB: 32 // L1: adr xA, sym 33 // L3: ldr xC, [xB, #imm] 34 // 35 // To be valid a LOH must meet all the requirements needed by all the related 36 // possible linker transformations. 37 // For instance, using the running example, the constraints to emit 38 // ".loh AdrpAddLdr" are: 39 // - L1, L2, and L3 instructions are of the expected type, i.e., 40 // respectively ADRP, ADD (immediate), and LD. 41 // - The result of L1 is used only by L2. 42 // - The register argument (xA) used in the ADD instruction is defined 43 // only by L1. 44 // - The result of L2 is used only by L3. 45 // - The base address (xB) in L3 is defined only L2. 46 // - The ADRP in L1 and the ADD in L2 must reference the same symbol using 47 // @PAGE/@PAGEOFF with no additional constants 48 // 49 // Currently supported LOHs are: 50 // * So called non-ADRP-related: 51 // - .loh AdrpAddLdr L1, L2, L3: 52 // L1: adrp xA, sym@PAGE 53 // L2: add xB, xA, sym@PAGEOFF 54 // L3: ldr xC, [xB, #imm] 55 // - .loh AdrpLdrGotLdr L1, L2, L3: 56 // L1: adrp xA, sym@GOTPAGE 57 // L2: ldr xB, [xA, sym@GOTPAGEOFF] 58 // L3: ldr xC, [xB, #imm] 59 // - .loh AdrpLdr L1, L3: 60 // L1: adrp xA, sym@PAGE 61 // L3: ldr xC, [xA, sym@PAGEOFF] 62 // - .loh AdrpAddStr L1, L2, L3: 63 // L1: adrp xA, sym@PAGE 64 // L2: add xB, xA, sym@PAGEOFF 65 // L3: str xC, [xB, #imm] 66 // - .loh AdrpLdrGotStr L1, L2, L3: 67 // L1: adrp xA, sym@GOTPAGE 68 // L2: ldr xB, [xA, sym@GOTPAGEOFF] 69 // L3: str xC, [xB, #imm] 70 // - .loh AdrpAdd L1, L2: 71 // L1: adrp xA, sym@PAGE 72 // L2: add xB, xA, sym@PAGEOFF 73 // For all these LOHs, L1, L2, L3 form a simple chain: 74 // L1 result is used only by L2 and L2 result by L3. 75 // L3 LOH-related argument is defined only by L2 and L2 LOH-related argument 76 // by L1. 77 // All these LOHs aim at using more efficient load/store patterns by folding 78 // some instructions used to compute the address directly into the load/store. 79 // 80 // * So called ADRP-related: 81 // - .loh AdrpAdrp L2, L1: 82 // L2: ADRP xA, sym1@PAGE 83 // L1: ADRP xA, sym2@PAGE 84 // L2 dominates L1 and xA is not redifined between L2 and L1 85 // This LOH aims at getting rid of redundant ADRP instructions. 86 // 87 // The overall design for emitting the LOHs is: 88 // 1. AArch64CollectLOH (this pass) records the LOHs in the AArch64FunctionInfo. 89 // 2. AArch64AsmPrinter reads the LOHs from AArch64FunctionInfo and it: 90 // 1. Associates them a label. 91 // 2. Emits them in a MCStreamer (EmitLOHDirective). 92 // - The MCMachOStreamer records them into the MCAssembler. 93 // - The MCAsmStreamer prints them. 94 // - Other MCStreamers ignore them. 95 // 3. Closes the MCStreamer: 96 // - The MachObjectWriter gets them from the MCAssembler and writes 97 // them in the object file. 98 // - Other ObjectWriters ignore them. 99 //===----------------------------------------------------------------------===// 100 101 #include "AArch64.h" 102 #include "AArch64InstrInfo.h" 103 #include "AArch64MachineFunctionInfo.h" 104 #include "AArch64Subtarget.h" 105 #include "MCTargetDesc/AArch64AddressingModes.h" 106 #include "llvm/ADT/BitVector.h" 107 #include "llvm/ADT/DenseMap.h" 108 #include "llvm/ADT/MapVector.h" 109 #include "llvm/ADT/SetVector.h" 110 #include "llvm/ADT/SmallVector.h" 111 #include "llvm/ADT/Statistic.h" 112 #include "llvm/CodeGen/MachineBasicBlock.h" 113 #include "llvm/CodeGen/MachineFunctionPass.h" 114 #include "llvm/CodeGen/MachineInstr.h" 115 #include "llvm/CodeGen/MachineInstrBuilder.h" 116 #include "llvm/Support/Debug.h" 117 #include "llvm/Support/ErrorHandling.h" 118 #include "llvm/Support/raw_ostream.h" 119 #include "llvm/Target/TargetMachine.h" 120 #include "llvm/Target/TargetRegisterInfo.h" 121 using namespace llvm; 122 123 #define DEBUG_TYPE "aarch64-collect-loh" 124 125 STATISTIC(NumADRPSimpleCandidate, 126 "Number of simplifiable ADRP dominate by another"); 127 STATISTIC(NumADDToSTR, "Number of simplifiable STR reachable by ADD"); 128 STATISTIC(NumLDRToSTR, "Number of simplifiable STR reachable by LDR"); 129 STATISTIC(NumADDToLDR, "Number of simplifiable LDR reachable by ADD"); 130 STATISTIC(NumLDRToLDR, "Number of simplifiable LDR reachable by LDR"); 131 STATISTIC(NumADRPToLDR, "Number of simplifiable LDR reachable by ADRP"); 132 STATISTIC(NumADRSimpleCandidate, "Number of simplifiable ADRP + ADD"); 133 134 #define AARCH64_COLLECT_LOH_NAME "AArch64 Collect Linker Optimization Hint (LOH)" 135 136 namespace { 137 138 struct AArch64CollectLOH : public MachineFunctionPass { 139 static char ID; 140 AArch64CollectLOH() : MachineFunctionPass(ID) {} 141 142 bool runOnMachineFunction(MachineFunction &MF) override; 143 144 MachineFunctionProperties getRequiredProperties() const override { 145 return MachineFunctionProperties().set( 146 MachineFunctionProperties::Property::NoVRegs); 147 } 148 149 StringRef getPassName() const override { return AARCH64_COLLECT_LOH_NAME; } 150 151 void getAnalysisUsage(AnalysisUsage &AU) const override { 152 MachineFunctionPass::getAnalysisUsage(AU); 153 AU.setPreservesAll(); 154 } 155 }; 156 157 char AArch64CollectLOH::ID = 0; 158 159 } // end anonymous namespace. 160 161 INITIALIZE_PASS(AArch64CollectLOH, "aarch64-collect-loh", 162 AARCH64_COLLECT_LOH_NAME, false, false) 163 164 static bool canAddBePartOfLOH(const MachineInstr &MI) { 165 // Check immediate to see if the immediate is an address. 166 switch (MI.getOperand(2).getType()) { 167 default: 168 return false; 169 case MachineOperand::MO_GlobalAddress: 170 case MachineOperand::MO_JumpTableIndex: 171 case MachineOperand::MO_ConstantPoolIndex: 172 case MachineOperand::MO_BlockAddress: 173 return true; 174 } 175 } 176 177 /// Answer the following question: Can Def be one of the definition 178 /// involved in a part of a LOH? 179 static bool canDefBePartOfLOH(const MachineInstr &MI) { 180 // Accept ADRP, ADDLow and LOADGot. 181 switch (MI.getOpcode()) { 182 default: 183 return false; 184 case AArch64::ADRP: 185 return true; 186 case AArch64::ADDXri: 187 return canAddBePartOfLOH(MI); 188 case AArch64::LDRXui: 189 // Check immediate to see if the immediate is an address. 190 switch (MI.getOperand(2).getType()) { 191 default: 192 return false; 193 case MachineOperand::MO_GlobalAddress: 194 return MI.getOperand(2).getTargetFlags() & AArch64II::MO_GOT; 195 } 196 } 197 } 198 199 /// Check whether the given instruction can the end of a LOH chain involving a 200 /// store. 201 static bool isCandidateStore(const MachineInstr &MI, const MachineOperand &MO) { 202 switch (MI.getOpcode()) { 203 default: 204 return false; 205 case AArch64::STRBBui: 206 case AArch64::STRHHui: 207 case AArch64::STRBui: 208 case AArch64::STRHui: 209 case AArch64::STRWui: 210 case AArch64::STRXui: 211 case AArch64::STRSui: 212 case AArch64::STRDui: 213 case AArch64::STRQui: 214 // We can only optimize the index operand. 215 // In case we have str xA, [xA, #imm], this is two different uses 216 // of xA and we cannot fold, otherwise the xA stored may be wrong, 217 // even if #imm == 0. 218 return MI.getOperandNo(&MO) == 1 && 219 MI.getOperand(0).getReg() != MI.getOperand(1).getReg(); 220 } 221 } 222 223 /// Check whether the given instruction can be the end of a LOH chain 224 /// involving a load. 225 static bool isCandidateLoad(const MachineInstr &MI) { 226 switch (MI.getOpcode()) { 227 default: 228 return false; 229 case AArch64::LDRSBWui: 230 case AArch64::LDRSBXui: 231 case AArch64::LDRSHWui: 232 case AArch64::LDRSHXui: 233 case AArch64::LDRSWui: 234 case AArch64::LDRBui: 235 case AArch64::LDRHui: 236 case AArch64::LDRWui: 237 case AArch64::LDRXui: 238 case AArch64::LDRSui: 239 case AArch64::LDRDui: 240 case AArch64::LDRQui: 241 return !(MI.getOperand(2).getTargetFlags() & AArch64II::MO_GOT); 242 } 243 } 244 245 /// Check whether the given instruction can load a litteral. 246 static bool supportLoadFromLiteral(const MachineInstr &MI) { 247 switch (MI.getOpcode()) { 248 default: 249 return false; 250 case AArch64::LDRSWui: 251 case AArch64::LDRWui: 252 case AArch64::LDRXui: 253 case AArch64::LDRSui: 254 case AArch64::LDRDui: 255 case AArch64::LDRQui: 256 return true; 257 } 258 } 259 260 /// Number of GPR registers traked by mapRegToGPRIndex() 261 static const unsigned N_GPR_REGS = 31; 262 /// Map register number to index from 0-30. 263 static int mapRegToGPRIndex(MCPhysReg Reg) { 264 static_assert(AArch64::X28 - AArch64::X0 + 3 == N_GPR_REGS, "Number of GPRs"); 265 static_assert(AArch64::W30 - AArch64::W0 + 1 == N_GPR_REGS, "Number of GPRs"); 266 if (AArch64::X0 <= Reg && Reg <= AArch64::X28) 267 return Reg - AArch64::X0; 268 if (AArch64::W0 <= Reg && Reg <= AArch64::W30) 269 return Reg - AArch64::W0; 270 // TableGen gives "FP" and "LR" an index not adjacent to X28 so we have to 271 // handle them as special cases. 272 if (Reg == AArch64::FP) 273 return 29; 274 if (Reg == AArch64::LR) 275 return 30; 276 return -1; 277 } 278 279 /// State tracked per register. 280 /// The main algorithm walks backwards over a basic block maintaining this 281 /// datastructure for each tracked general purpose register. 282 struct LOHInfo { 283 MCLOHType Type : 8; ///< "Best" type of LOH possible. 284 bool IsCandidate : 1; ///< Possible LOH candidate. 285 bool OneUser : 1; ///< Found exactly one user (yet). 286 bool MultiUsers : 1; ///< Found multiple users. 287 const MachineInstr *MI0; ///< First instruction involved in the LOH. 288 const MachineInstr *MI1; ///< Second instruction involved in the LOH 289 /// (if any). 290 const MachineInstr *LastADRP; ///< Last ADRP in same register. 291 }; 292 293 /// Update state \p Info given \p MI uses the tracked register. 294 static void handleUse(const MachineInstr &MI, const MachineOperand &MO, 295 LOHInfo &Info) { 296 // We have multiple uses if we already found one before. 297 if (Info.MultiUsers || Info.OneUser) { 298 Info.IsCandidate = false; 299 Info.MultiUsers = true; 300 return; 301 } 302 Info.OneUser = true; 303 304 // Start new LOHInfo if applicable. 305 if (isCandidateLoad(MI)) { 306 Info.Type = MCLOH_AdrpLdr; 307 Info.IsCandidate = true; 308 Info.MI0 = &MI; 309 // Note that even this is AdrpLdr now, we can switch to a Ldr variant 310 // later. 311 } else if (isCandidateStore(MI, MO)) { 312 Info.Type = MCLOH_AdrpAddStr; 313 Info.IsCandidate = true; 314 Info.MI0 = &MI; 315 Info.MI1 = nullptr; 316 } else if (MI.getOpcode() == AArch64::ADDXri) { 317 Info.Type = MCLOH_AdrpAdd; 318 Info.IsCandidate = true; 319 Info.MI0 = &MI; 320 } else if (MI.getOpcode() == AArch64::LDRXui && 321 MI.getOperand(2).getTargetFlags() & AArch64II::MO_GOT) { 322 Info.Type = MCLOH_AdrpLdrGot; 323 Info.IsCandidate = true; 324 Info.MI0 = &MI; 325 } 326 } 327 328 /// Update state \p Info given the tracked register is clobbered. 329 static void handleClobber(LOHInfo &Info) { 330 Info.IsCandidate = false; 331 Info.OneUser = false; 332 Info.MultiUsers = false; 333 Info.LastADRP = nullptr; 334 } 335 336 /// Update state \p Info given that \p MI is possibly the middle instruction 337 /// of an LOH involving 3 instructions. 338 static bool handleMiddleInst(const MachineInstr &MI, LOHInfo &DefInfo, 339 LOHInfo &OpInfo) { 340 if (!DefInfo.IsCandidate || (&DefInfo != &OpInfo && OpInfo.OneUser)) 341 return false; 342 // Copy LOHInfo for dest register to LOHInfo for source register. 343 if (&DefInfo != &OpInfo) { 344 OpInfo = DefInfo; 345 // Invalidate \p DefInfo because we track it in \p OpInfo now. 346 handleClobber(DefInfo); 347 } else 348 DefInfo.LastADRP = nullptr; 349 350 // Advance state machine. 351 assert(OpInfo.IsCandidate && "Expect valid state"); 352 if (MI.getOpcode() == AArch64::ADDXri && canAddBePartOfLOH(MI)) { 353 if (OpInfo.Type == MCLOH_AdrpLdr) { 354 OpInfo.Type = MCLOH_AdrpAddLdr; 355 OpInfo.IsCandidate = true; 356 OpInfo.MI1 = &MI; 357 return true; 358 } else if (OpInfo.Type == MCLOH_AdrpAddStr && OpInfo.MI1 == nullptr) { 359 OpInfo.Type = MCLOH_AdrpAddStr; 360 OpInfo.IsCandidate = true; 361 OpInfo.MI1 = &MI; 362 return true; 363 } 364 } else { 365 assert(MI.getOpcode() == AArch64::LDRXui && "Expect LDRXui"); 366 assert((MI.getOperand(2).getTargetFlags() & AArch64II::MO_GOT) && 367 "Expected GOT relocation"); 368 if (OpInfo.Type == MCLOH_AdrpAddStr && OpInfo.MI1 == nullptr) { 369 OpInfo.Type = MCLOH_AdrpLdrGotStr; 370 OpInfo.IsCandidate = true; 371 OpInfo.MI1 = &MI; 372 return true; 373 } else if (OpInfo.Type == MCLOH_AdrpLdr) { 374 OpInfo.Type = MCLOH_AdrpLdrGotLdr; 375 OpInfo.IsCandidate = true; 376 OpInfo.MI1 = &MI; 377 return true; 378 } 379 } 380 return false; 381 } 382 383 /// Update state when seeing and ADRP instruction. 384 static void handleADRP(const MachineInstr &MI, AArch64FunctionInfo &AFI, 385 LOHInfo &Info) { 386 if (Info.LastADRP != nullptr) { 387 DEBUG(dbgs() << "Adding MCLOH_AdrpAdrp:\n" << '\t' << MI << '\t' 388 << *Info.LastADRP); 389 AFI.addLOHDirective(MCLOH_AdrpAdrp, {&MI, Info.LastADRP}); 390 ++NumADRPSimpleCandidate; 391 } 392 393 // Produce LOH directive if possible. 394 if (Info.IsCandidate) { 395 switch (Info.Type) { 396 case MCLOH_AdrpAdd: 397 DEBUG(dbgs() << "Adding MCLOH_AdrpAdd:\n" << '\t' << MI << '\t' 398 << *Info.MI0); 399 AFI.addLOHDirective(MCLOH_AdrpAdd, {&MI, Info.MI0}); 400 ++NumADRSimpleCandidate; 401 break; 402 case MCLOH_AdrpLdr: 403 if (supportLoadFromLiteral(*Info.MI0)) { 404 DEBUG(dbgs() << "Adding MCLOH_AdrpLdr:\n" << '\t' << MI << '\t' 405 << *Info.MI0); 406 AFI.addLOHDirective(MCLOH_AdrpLdr, {&MI, Info.MI0}); 407 ++NumADRPToLDR; 408 } 409 break; 410 case MCLOH_AdrpAddLdr: 411 DEBUG(dbgs() << "Adding MCLOH_AdrpAddLdr:\n" << '\t' << MI << '\t' 412 << *Info.MI1 << '\t' << *Info.MI0); 413 AFI.addLOHDirective(MCLOH_AdrpAddLdr, {&MI, Info.MI1, Info.MI0}); 414 ++NumADDToLDR; 415 break; 416 case MCLOH_AdrpAddStr: 417 if (Info.MI1 != nullptr) { 418 DEBUG(dbgs() << "Adding MCLOH_AdrpAddStr:\n" << '\t' << MI << '\t' 419 << *Info.MI1 << '\t' << *Info.MI0); 420 AFI.addLOHDirective(MCLOH_AdrpAddStr, {&MI, Info.MI1, Info.MI0}); 421 ++NumADDToSTR; 422 } 423 break; 424 case MCLOH_AdrpLdrGotLdr: 425 DEBUG(dbgs() << "Adding MCLOH_AdrpLdrGotLdr:\n" << '\t' << MI << '\t' 426 << *Info.MI1 << '\t' << *Info.MI0); 427 AFI.addLOHDirective(MCLOH_AdrpLdrGotLdr, {&MI, Info.MI1, Info.MI0}); 428 ++NumLDRToLDR; 429 break; 430 case MCLOH_AdrpLdrGotStr: 431 DEBUG(dbgs() << "Adding MCLOH_AdrpLdrGotStr:\n" << '\t' << MI << '\t' 432 << *Info.MI1 << '\t' << *Info.MI0); 433 AFI.addLOHDirective(MCLOH_AdrpLdrGotStr, {&MI, Info.MI1, Info.MI0}); 434 ++NumLDRToSTR; 435 break; 436 case MCLOH_AdrpLdrGot: 437 DEBUG(dbgs() << "Adding MCLOH_AdrpLdrGot:\n" << '\t' << MI << '\t' 438 << *Info.MI0); 439 AFI.addLOHDirective(MCLOH_AdrpLdrGot, {&MI, Info.MI0}); 440 break; 441 case MCLOH_AdrpAdrp: 442 llvm_unreachable("MCLOH_AdrpAdrp not used in state machine"); 443 } 444 } 445 446 handleClobber(Info); 447 Info.LastADRP = &MI; 448 } 449 450 static void handleRegMaskClobber(const uint32_t *RegMask, MCPhysReg Reg, 451 LOHInfo *LOHInfos) { 452 if (!MachineOperand::clobbersPhysReg(RegMask, Reg)) 453 return; 454 int Idx = mapRegToGPRIndex(Reg); 455 if (Idx >= 0) 456 handleClobber(LOHInfos[Idx]); 457 } 458 459 static void handleNormalInst(const MachineInstr &MI, LOHInfo *LOHInfos) { 460 // Handle defs and regmasks. 461 for (const MachineOperand &MO : MI.operands()) { 462 if (MO.isRegMask()) { 463 const uint32_t *RegMask = MO.getRegMask(); 464 for (MCPhysReg Reg : AArch64::GPR32RegClass) 465 handleRegMaskClobber(RegMask, Reg, LOHInfos); 466 for (MCPhysReg Reg : AArch64::GPR64RegClass) 467 handleRegMaskClobber(RegMask, Reg, LOHInfos); 468 continue; 469 } 470 if (!MO.isReg() || !MO.isDef()) 471 continue; 472 int Idx = mapRegToGPRIndex(MO.getReg()); 473 if (Idx < 0) 474 continue; 475 handleClobber(LOHInfos[Idx]); 476 } 477 // Handle uses. 478 for (const MachineOperand &MO : MI.uses()) { 479 if (!MO.isReg() || !MO.readsReg()) 480 continue; 481 int Idx = mapRegToGPRIndex(MO.getReg()); 482 if (Idx < 0) 483 continue; 484 handleUse(MI, MO, LOHInfos[Idx]); 485 } 486 } 487 488 bool AArch64CollectLOH::runOnMachineFunction(MachineFunction &MF) { 489 if (skipFunction(*MF.getFunction())) 490 return false; 491 492 DEBUG(dbgs() << "********** AArch64 Collect LOH **********\n" 493 << "Looking in function " << MF.getName() << '\n'); 494 495 LOHInfo LOHInfos[N_GPR_REGS]; 496 AArch64FunctionInfo &AFI = *MF.getInfo<AArch64FunctionInfo>(); 497 for (const MachineBasicBlock &MBB : MF) { 498 // Reset register tracking state. 499 memset(LOHInfos, 0, sizeof(LOHInfos)); 500 // Live-out registers are used. 501 for (const MachineBasicBlock *Succ : MBB.successors()) { 502 for (const auto &LI : Succ->liveins()) { 503 int RegIdx = mapRegToGPRIndex(LI.PhysReg); 504 if (RegIdx >= 0) 505 LOHInfos[RegIdx].OneUser = true; 506 } 507 } 508 509 // Walk the basic block backwards and update the per register state machine 510 // in the process. 511 for (const MachineInstr &MI : make_range(MBB.rbegin(), MBB.rend())) { 512 unsigned Opcode = MI.getOpcode(); 513 switch (Opcode) { 514 case AArch64::ADDXri: 515 case AArch64::LDRXui: 516 if (canDefBePartOfLOH(MI)) { 517 const MachineOperand &Def = MI.getOperand(0); 518 const MachineOperand &Op = MI.getOperand(1); 519 assert(Def.isReg() && Def.isDef() && "Expected reg def"); 520 assert(Op.isReg() && Op.isUse() && "Expected reg use"); 521 int DefIdx = mapRegToGPRIndex(Def.getReg()); 522 int OpIdx = mapRegToGPRIndex(Op.getReg()); 523 if (DefIdx >= 0 && OpIdx >= 0 && 524 handleMiddleInst(MI, LOHInfos[DefIdx], LOHInfos[OpIdx])) 525 continue; 526 } 527 break; 528 case AArch64::ADRP: 529 const MachineOperand &Op0 = MI.getOperand(0); 530 int Idx = mapRegToGPRIndex(Op0.getReg()); 531 if (Idx >= 0) { 532 handleADRP(MI, AFI, LOHInfos[Idx]); 533 continue; 534 } 535 break; 536 } 537 handleNormalInst(MI, LOHInfos); 538 } 539 } 540 541 // Return "no change": The pass only collects information. 542 return false; 543 } 544 545 FunctionPass *llvm::createAArch64CollectLOHPass() { 546 return new AArch64CollectLOH(); 547 } 548