1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #ifndef ART_COMPILER_OPTIMIZING_LOOP_OPTIMIZATION_H_ 18 #define ART_COMPILER_OPTIMIZING_LOOP_OPTIMIZATION_H_ 19 20 #include "base/macros.h" 21 #include "base/scoped_arena_allocator.h" 22 #include "base/scoped_arena_containers.h" 23 #include "induction_var_range.h" 24 #include "loop_analysis.h" 25 #include "nodes.h" 26 #include "optimization.h" 27 #include "superblock_cloner.h" 28 29 namespace art HIDDEN { 30 31 class CompilerOptions; 32 class ArchNoOptsLoopHelper; 33 34 /** 35 * Loop optimizations. Builds a loop hierarchy and applies optimizations to 36 * the detected nested loops, such as removal of dead induction and empty loops 37 * and inner loop vectorization. 38 */ 39 class HLoopOptimization : public HOptimization { 40 public: 41 HLoopOptimization(HGraph* graph, 42 const CodeGenerator& codegen, // Needs info about the target. 43 HInductionVarAnalysis* induction_analysis, 44 OptimizingCompilerStats* stats, 45 const char* name = kLoopOptimizationPassName); 46 47 bool Run() override; 48 49 static constexpr const char* kLoopOptimizationPassName = "loop_optimization"; 50 51 // The maximum number of total instructions (trip_count * instruction_count), 52 // where the optimization of removing SuspendChecks from the loop header could 53 // be performed. 54 static constexpr int64_t kMaxTotalInstRemoveSuspendCheck = 128; 55 56 private: 57 /** 58 * A single loop inside the loop hierarchy representation. 59 */ 60 struct LoopNode : public ArenaObject<kArenaAllocLoopOptimization> { LoopNodeLoopNode61 explicit LoopNode(HLoopInformation* lp_info) 62 : loop_info(lp_info), 63 outer(nullptr), 64 inner(nullptr), 65 previous(nullptr), 66 next(nullptr), 67 try_catch_kind(TryCatchKind::kUnknown) {} 68 69 enum class TryCatchKind { 70 kUnknown, 71 // Either if we have a try catch in the loop, or if the loop is inside of an outer try catch, 72 // we set `kHasTryCatch`. 73 kHasTryCatch, 74 kNoTryCatch 75 }; 76 77 HLoopInformation* loop_info; 78 LoopNode* outer; 79 LoopNode* inner; 80 LoopNode* previous; 81 LoopNode* next; 82 TryCatchKind try_catch_kind; 83 }; 84 85 /* 86 * Vectorization restrictions (bit mask). 87 */ 88 enum VectorRestrictions { 89 kNone = 0, // no restrictions 90 kNoMul = 1 << 0, // no multiplication 91 kNoDiv = 1 << 1, // no division 92 kNoShift = 1 << 2, // no shift 93 kNoShr = 1 << 3, // no arithmetic shift right 94 kNoHiBits = 1 << 4, // "wider" operations cannot bring in higher order bits 95 kNoSignedHAdd = 1 << 5, // no signed halving add 96 kNoUnsignedHAdd = 1 << 6, // no unsigned halving add 97 kNoUnroundedHAdd = 1 << 7, // no unrounded halving add 98 kNoAbs = 1 << 8, // no absolute value 99 kNoStringCharAt = 1 << 9, // no StringCharAt 100 kNoReduction = 1 << 10, // no reduction 101 kNoSAD = 1 << 11, // no sum of absolute differences (SAD) 102 kNoWideSAD = 1 << 12, // no sum of absolute differences (SAD) with operand widening 103 kNoDotProd = 1 << 13, // no dot product 104 kNoIfCond = 1 << 14, // no if condition conversion 105 }; 106 107 /* 108 * Loop synthesis mode during vectorization 109 * (sequential peeling/cleanup loop or vector loop). 110 */ 111 enum class LoopSynthesisMode { 112 kSequential, 113 kVector 114 }; 115 friend std::ostream& operator<<(std::ostream& os, const LoopSynthesisMode& fd_logger); 116 117 /* 118 * Representation of a unit-stride array reference. 119 */ 120 struct ArrayReference { 121 ArrayReference(HInstruction* b, HInstruction* o, DataType::Type t, bool l, bool c = false) baseArrayReference122 : base(b), offset(o), type(t), lhs(l), is_string_char_at(c) { } 123 bool operator<(const ArrayReference& other) const { 124 return 125 (base < other.base) || 126 (base == other.base && 127 (offset < other.offset || (offset == other.offset && 128 (type < other.type || 129 (type == other.type && 130 (lhs < other.lhs || 131 (lhs == other.lhs && 132 is_string_char_at < other.is_string_char_at))))))); 133 } 134 HInstruction* base; // base address 135 HInstruction* offset; // offset + i 136 DataType::Type type; // component type 137 bool lhs; // def/use 138 bool is_string_char_at; // compressed string read 139 }; 140 141 // This structure describes the control flow (CF) -> data flow (DF) conversion of the loop 142 // with control flow (see below) for the purpose of predicated autovectorization. 143 // 144 // Lets define "loops without control-flow" (or non-CF loops) as loops with two consecutive 145 // blocks and without the branching structure except for the loop exit. And 146 // "loop with control-flow" (or CF-loops) - all other loops. 147 // 148 // In the execution of the original CF-loop on each iteration some basic block Y will be 149 // either executed or not executed, depending on the control flow of the loop. More 150 // specifically, a block will be executed if all the conditional branches of the nodes in 151 // the control dependency graph for that block Y are taken according to the path from the loop 152 // header to that basic block. 153 // 154 // This is the key idea of CF->DF conversion: a boolean value 155 // 'ctrl_pred == cond1 && cond2 && ...' will determine whether the basic block Y will be 156 // executed, where cond_K is whether the branch of the node K in the control dependency 157 // graph upward traversal was taken in the 'right' direction. 158 // 159 // Def.: BB Y is control dependent on BB X iff 160 // (1) there exists a directed path P from X to Y with any basic block Z in P (excluding X 161 // and Y) post-dominated by Y and 162 // (2) X is not post-dominated by Y. 163 // ... 164 // X 165 // false / \ true 166 // / \ 167 // ... 168 // | 169 // Y 170 // ... 171 // 172 // When doing predicated autovectorization of a CF loop, we use the CF->DF conversion approach: 173 // 1) do the data analysis and vector operation creation as if it was a non-CF loop. 174 // 2) for each HIf block create two vector predicate setting instructions - for True and False 175 // edges/paths. 176 // 3) assign a governing vector predicate (see comments near HVecPredSetOperation) 177 // to each vector operation Alpha in the loop (including to those vector predicate setting 178 // instructions created in #2); do this by: 179 // - finding the immediate control dependent block of the instruction Alpha's block. 180 // - choosing the True or False predicate setting instruction (created in #2) depending 181 // on the path to the instruction. 182 // 183 // For more information check the papers: 184 // 185 // - Allen, John R and Kennedy, Ken and Porterfield, Carrie and Warren, Joe, 186 // “Conversion of Control Dependence to Data Dependence,” in Proceedings of the 10th ACM 187 // SIGACT-SIGPLAN Symposium on Principles of Programming Languages, 1983, pp. 177–189. 188 // - JEANNE FERRANTE, KARL J. OTTENSTEIN, JOE D. WARREN, 189 // "The Program Dependence Graph and Its Use in Optimization" 190 // 191 class BlockPredicateInfo : public ArenaObject<kArenaAllocLoopOptimization> { 192 public: BlockPredicateInfo()193 BlockPredicateInfo() : 194 control_predicate_(nullptr), 195 true_predicate_(nullptr), 196 false_predicate_(nullptr) {} 197 SetControlFlowInfo(HVecPredSetOperation * true_predicate,HVecPredSetOperation * false_predicate)198 void SetControlFlowInfo(HVecPredSetOperation* true_predicate, 199 HVecPredSetOperation* false_predicate) { 200 DCHECK(!HasControlFlowOps()); 201 true_predicate_ = true_predicate; 202 false_predicate_ = false_predicate; 203 } 204 HasControlFlowOps()205 bool HasControlFlowOps() const { 206 // Note: a block must have both T/F predicates set or none of them. 207 DCHECK_EQ(true_predicate_ == nullptr, false_predicate_ == nullptr); 208 return true_predicate_ != nullptr; 209 } 210 GetControlPredicate()211 HVecPredSetOperation* GetControlPredicate() const { return control_predicate_; } SetControlPredicate(HVecPredSetOperation * control_predicate)212 void SetControlPredicate(HVecPredSetOperation* control_predicate) { 213 control_predicate_ = control_predicate; 214 } 215 GetTruePredicate()216 HVecPredSetOperation* GetTruePredicate() const { return true_predicate_; } GetFalsePredicate()217 HVecPredSetOperation* GetFalsePredicate() const { return false_predicate_; } 218 219 private: 220 // Vector control predicate operation, associated with the block which will determine 221 // the active lanes for all vector operations, originated from this block. 222 HVecPredSetOperation* control_predicate_; 223 224 // Vector predicate instruction, associated with the true sucessor of the block. 225 HVecPredSetOperation* true_predicate_; 226 // Vector predicate instruction, associated with the false sucessor of the block. 227 HVecPredSetOperation* false_predicate_; 228 }; 229 230 // 231 // Loop setup and traversal. 232 // 233 234 bool LocalRun(); 235 void AddLoop(HLoopInformation* loop_info); 236 void RemoveLoop(LoopNode* node); 237 238 // Traverses all loops inner to outer to perform simplifications and optimizations. 239 // Returns true if loops nested inside current loop (node) have changed. 240 bool TraverseLoopsInnerToOuter(LoopNode* node); 241 242 // Calculates `node`'s `try_catch_kind` and sets it to: 243 // 1) kHasTryCatch if it has try catches (or if it's inside of an outer try catch) 244 // 2) kNoTryCatch otherwise. 245 void CalculateAndSetTryCatchKind(LoopNode* node); 246 247 // 248 // Optimization. 249 // 250 251 void SimplifyInduction(LoopNode* node); 252 void SimplifyBlocks(LoopNode* node); 253 254 // Performs optimizations specific to inner loop with finite header logic (empty loop removal, 255 // unrolling, vectorization). Returns true if anything changed. 256 bool TryOptimizeInnerLoopFinite(LoopNode* node); 257 258 // Performs optimizations specific to inner loop. Returns true if anything changed. 259 bool OptimizeInnerLoop(LoopNode* node); 260 261 // Tries to apply loop unrolling for branch penalty reduction and better instruction scheduling 262 // opportunities. Returns whether transformation happened. 'generate_code' determines whether the 263 // optimization should be actually applied. 264 bool TryUnrollingForBranchPenaltyReduction(LoopAnalysisInfo* analysis_info, 265 bool generate_code = true); 266 267 // Tries to apply loop peeling for loop invariant exits elimination. Returns whether 268 // transformation happened. 'generate_code' determines whether the optimization should be 269 // actually applied. 270 bool TryPeelingForLoopInvariantExitsElimination(LoopAnalysisInfo* analysis_info, 271 bool generate_code = true); 272 273 // Tries to perform whole loop unrolling for a small loop with a small trip count to eliminate 274 // the loop check overhead and to have more opportunities for inter-iteration optimizations. 275 // Returns whether transformation happened. 'generate_code' determines whether the optimization 276 // should be actually applied. 277 bool TryFullUnrolling(LoopAnalysisInfo* analysis_info, bool generate_code = true); 278 279 // Tries to remove SuspendCheck for plain loops with a low trip count. The 280 // SuspendCheck in the codegen makes sure that the thread can be interrupted 281 // during execution for GC. Not being able to do so might decrease the 282 // responsiveness of GC when a very long loop or a long recursion is being 283 // executed. However, for plain loops with a small trip count, the removal of 284 // SuspendCheck should not affect the GC's responsiveness by a large margin. 285 // Consequently, since the thread won't be interrupted for plain loops, it is 286 // assumed that the performance might increase by removing SuspendCheck. 287 bool TryToRemoveSuspendCheckFromLoopHeader(LoopAnalysisInfo* analysis_info, 288 bool generate_code = true); 289 290 // Tries to apply scalar loop optimizations. 291 bool TryLoopScalarOpts(LoopNode* node); 292 293 // 294 // Vectorization analysis and synthesis. 295 // 296 297 // Returns whether the data flow requirements are met for vectorization. 298 // 299 // - checks whether instructions are vectorizable for the target. 300 // - conducts data dependence analysis for array references. 301 // - additionally, collects info on peeling and aligment strategy. 302 bool CanVectorizeDataFlow(LoopNode* node, HBasicBlock* header, bool collect_alignment_info); 303 304 // Does the checks (common for predicated and traditional mode) for the loop. 305 bool ShouldVectorizeCommon(LoopNode* node, HPhi* main_phi, int64_t trip_count); 306 307 // Try to vectorize the loop, returns whether it was successful. 308 // 309 // There are two versions/algorithms: 310 // - Predicated: all the vector operations have governing predicates which control 311 // which individual vector lanes will be active (see HVecPredSetOperation for more details). 312 // Example: vectorization using AArch64 SVE. 313 // - Traditional: a regular mode in which all vector operations lanes are unconditionally 314 // active. 315 // Example: vectoriation using AArch64 NEON. 316 bool TryVectorizePredicated(LoopNode* node, 317 HBasicBlock* body, 318 HBasicBlock* exit, 319 HPhi* main_phi, 320 int64_t trip_count); 321 322 bool TryVectorizedTraditional(LoopNode* node, 323 HBasicBlock* body, 324 HBasicBlock* exit, 325 HPhi* main_phi, 326 int64_t trip_count); 327 328 // Vectorizes the loop for which all checks have been already done. 329 void VectorizePredicated(LoopNode* node, 330 HBasicBlock* block, 331 HBasicBlock* exit); 332 void VectorizeTraditional(LoopNode* node, 333 HBasicBlock* block, 334 HBasicBlock* exit, 335 int64_t trip_count); 336 337 // Performs final steps for whole vectorization process: links reduction, removes the original 338 // scalar loop, updates loop info. 339 void FinalizeVectorization(LoopNode* node); 340 341 // Helpers that do the vector instruction synthesis for the previously created loop; create 342 // and fill the loop body with instructions. 343 // 344 // A version to generate a vector loop in predicated mode. 345 void GenerateNewLoopPredicated(LoopNode* node, 346 HBasicBlock* new_preheader, 347 HInstruction* lo, 348 HInstruction* hi, 349 HInstruction* step); 350 351 // A version to generate a vector loop in traditional mode or to generate 352 // a scalar loop for both modes. 353 void GenerateNewLoopScalarOrTraditional(LoopNode* node, 354 HBasicBlock* new_preheader, 355 HInstruction* lo, 356 HInstruction* hi, 357 HInstruction* step, 358 uint32_t unroll); 359 360 // 361 // Helpers for GenerateNewLoop*. 362 // 363 364 // Updates vectorization bookkeeping date for the new loop, creates and returns 365 // its main induction Phi. 366 HPhi* InitializeForNewLoop(HBasicBlock* new_preheader, HInstruction* lo); 367 368 // Finalizes reduction and induction phis' inputs for the newly created loop. 369 void FinalizePhisForNewLoop(HPhi* phi, HInstruction* lo); 370 371 // Creates empty predicate info object for each basic block and puts it into the map. 372 void PreparePredicateInfoMap(LoopNode* node); 373 374 // Set up block true/false predicates using info, collected through data flow and control 375 // dependency analysis. 376 void InitPredicateInfoMap(LoopNode* node, HVecPredSetOperation* loop_main_pred); 377 378 // Performs instruction synthesis for the loop body. 379 void GenerateNewLoopBodyOnce(LoopNode* node, 380 DataType::Type induc_type, 381 HInstruction* step); 382 383 // Returns whether the vector loop needs runtime disambiguation test for array refs. NeedsArrayRefsDisambiguationTest()384 bool NeedsArrayRefsDisambiguationTest() const { return vector_runtime_test_a_ != nullptr; } 385 386 bool VectorizeDef(LoopNode* node, HInstruction* instruction, bool generate_code); 387 bool VectorizeUse(LoopNode* node, 388 HInstruction* instruction, 389 bool generate_code, 390 DataType::Type type, 391 uint64_t restrictions); 392 uint32_t GetVectorSizeInBytes(); 393 bool TrySetVectorType(DataType::Type type, /*out*/ uint64_t* restrictions); 394 bool TrySetVectorLengthImpl(uint32_t length); 395 TrySetVectorLength(DataType::Type type,uint32_t length)396 bool TrySetVectorLength(DataType::Type type, uint32_t length) { 397 bool res = TrySetVectorLengthImpl(length); 398 // Currently the vectorizer supports only the mode when full SIMD registers are used. 399 DCHECK_IMPLIES(res, DataType::Size(type) * length == GetVectorSizeInBytes()); 400 return res; 401 } 402 403 void GenerateVecInv(HInstruction* org, DataType::Type type); 404 void GenerateVecSub(HInstruction* org, HInstruction* offset); 405 void GenerateVecMem(HInstruction* org, 406 HInstruction* opa, 407 HInstruction* opb, 408 HInstruction* offset, 409 DataType::Type type); 410 void GenerateVecReductionPhi(HPhi* phi); 411 void GenerateVecReductionPhiInputs(HPhi* phi, HInstruction* reduction); 412 HInstruction* ReduceAndExtractIfNeeded(HInstruction* instruction); 413 HInstruction* GenerateVecOp(HInstruction* org, 414 HInstruction* opa, 415 HInstruction* opb, 416 DataType::Type type); 417 418 // Vectorization idioms. 419 bool VectorizeSaturationIdiom(LoopNode* node, 420 HInstruction* instruction, 421 bool generate_code, 422 DataType::Type type, 423 uint64_t restrictions); 424 bool VectorizeHalvingAddIdiom(LoopNode* node, 425 HInstruction* instruction, 426 bool generate_code, 427 DataType::Type type, 428 uint64_t restrictions); 429 bool VectorizeSADIdiom(LoopNode* node, 430 HInstruction* instruction, 431 bool generate_code, 432 DataType::Type type, 433 uint64_t restrictions); 434 bool VectorizeDotProdIdiom(LoopNode* node, 435 HInstruction* instruction, 436 bool generate_code, 437 DataType::Type type, 438 uint64_t restrictions); 439 bool VectorizeIfCondition(LoopNode* node, 440 HInstruction* instruction, 441 bool generate_code, 442 uint64_t restrictions); 443 444 // Vectorization heuristics. 445 Alignment ComputeAlignment(HInstruction* offset, 446 DataType::Type type, 447 bool is_string_char_at, 448 uint32_t peeling = 0); 449 void SetAlignmentStrategy(const ScopedArenaVector<uint32_t>& peeling_votes, 450 const ArrayReference* peeling_candidate); 451 uint32_t MaxNumberPeeled(); 452 bool IsVectorizationProfitable(int64_t trip_count); 453 454 // 455 // Helpers. 456 // 457 458 bool TrySetPhiInduction(HPhi* phi, bool restrict_uses); 459 bool TrySetPhiReduction(HPhi* phi); 460 461 // Detects loop header with a single induction (returned in main_phi), possibly 462 // other phis for reductions, but no other side effects. Returns true on success. 463 bool TrySetSimpleLoopHeader(HBasicBlock* block, /*out*/ HPhi** main_phi); 464 465 bool IsEmptyBody(HBasicBlock* block); 466 bool IsOnlyUsedAfterLoop(HLoopInformation* loop_info, 467 HInstruction* instruction, 468 bool collect_loop_uses, 469 /*out*/ uint32_t* use_count); 470 bool IsUsedOutsideLoop(HLoopInformation* loop_info, 471 HInstruction* instruction); 472 bool TryReplaceWithLastValue(HLoopInformation* loop_info, 473 HInstruction* instruction, 474 HBasicBlock* block); 475 bool TryAssignLastValue(HLoopInformation* loop_info, 476 HInstruction* instruction, 477 HBasicBlock* block, 478 bool collect_loop_uses); 479 void RemoveDeadInstructions(const HInstructionList& list); 480 bool CanRemoveCycle(); // Whether the current 'iset_' is removable. 481 IsInPredicatedVectorizationMode()482 bool IsInPredicatedVectorizationMode() const { return predicated_vectorization_mode_; } 483 void MaybeInsertInVectorExternalSet(HInstruction* instruction); 484 485 // Compiler options (to query ISA features). 486 const CompilerOptions* compiler_options_; 487 488 // Cached target SIMD vector register size in bytes. 489 const size_t simd_register_size_; 490 491 // Range information based on prior induction variable analysis. 492 InductionVarRange induction_range_; 493 494 // Phase-local heap memory allocator for the loop optimizer. Storage obtained 495 // through this allocator is immediately released when the loop optimizer is done. 496 ScopedArenaAllocator* loop_allocator_; 497 498 // Global heap memory allocator. Used to build HIR. 499 ArenaAllocator* global_allocator_; 500 501 // Entries into the loop hierarchy representation. The hierarchy resides 502 // in phase-local heap memory. 503 LoopNode* top_loop_; 504 LoopNode* last_loop_; 505 506 // Temporary bookkeeping of a set of instructions. 507 // Contents reside in phase-local heap memory. 508 ScopedArenaSet<HInstruction*>* iset_; 509 510 // Temporary bookkeeping of reduction instructions. Mapping is two-fold: 511 // (1) reductions in the loop-body are mapped back to their phi definition, 512 // (2) phi definitions are mapped to their initial value (updated during 513 // code generation to feed the proper values into the new chain). 514 // Contents reside in phase-local heap memory. 515 ScopedArenaSafeMap<HInstruction*, HInstruction*>* reductions_; 516 517 // Flag that tracks if any simplifications have occurred. 518 bool simplified_; 519 520 // Whether to use predicated loop vectorization (e.g. for arm64 SVE target). 521 bool predicated_vectorization_mode_; 522 523 // Number of "lanes" for selected packed type. 524 uint32_t vector_length_; 525 526 // Set of array references in the vector loop. 527 // Contents reside in phase-local heap memory. 528 ScopedArenaSet<ArrayReference>* vector_refs_; 529 530 // Static or dynamic loop peeling for alignment. 531 uint32_t vector_static_peeling_factor_; 532 const ArrayReference* vector_dynamic_peeling_candidate_; 533 534 // Dynamic data dependence test of the form a != b. 535 HInstruction* vector_runtime_test_a_; 536 HInstruction* vector_runtime_test_b_; 537 538 // Mapping used during vectorization synthesis for both the scalar peeling/cleanup 539 // loop (mode is kSequential) and the actual vector loop (mode is kVector). The data 540 // structure maps original instructions into the new instructions. 541 // Contents reside in phase-local heap memory. 542 ScopedArenaSafeMap<HInstruction*, HInstruction*>* vector_map_; 543 544 // Permanent mapping used during vectorization synthesis. 545 // Contents reside in phase-local heap memory. 546 ScopedArenaSafeMap<HInstruction*, HInstruction*>* vector_permanent_map_; 547 548 // Tracks vector operations that are inserted outside of the loop (preheader, exit) 549 // as part of vectorization (e.g. replicate scalar for loop invariants and reduce ops 550 // for loop reductions). 551 // 552 // The instructions in the set are live for the whole vectorization process of the current 553 // loop, not just during generation of a particular loop version (as the sets above). 554 // 555 // Currently the set is being only used in the predicated mode - for assigning governing 556 // predicates. 557 ScopedArenaSet<HInstruction*>* vector_external_set_; 558 559 // A mapping between a basic block of the original loop and its associated PredicateInfo. 560 // 561 // Only used in predicated loop vectorization mode. 562 ScopedArenaSafeMap<HBasicBlock*, BlockPredicateInfo*>* predicate_info_map_; 563 564 // Temporary vectorization bookkeeping. 565 LoopSynthesisMode synthesis_mode_; // synthesis mode 566 HBasicBlock* vector_preheader_; // preheader of the new loop 567 HBasicBlock* vector_header_; // header of the new loop 568 HBasicBlock* vector_body_; // body of the new loop 569 HInstruction* vector_index_; // normalized index of the new loop 570 571 // Helper for target-specific behaviour for loop optimizations. 572 ArchNoOptsLoopHelper* arch_loop_helper_; 573 574 friend class LoopOptimizationTest; 575 576 DISALLOW_COPY_AND_ASSIGN(HLoopOptimization); 577 }; 578 579 } // namespace art 580 581 #endif // ART_COMPILER_OPTIMIZING_LOOP_OPTIMIZATION_H_ 582