1 /*
2 * Copyright (C) 2014 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_OPTIMIZING_UNIT_TEST_H_
18 #define ART_COMPILER_OPTIMIZING_OPTIMIZING_UNIT_TEST_H_
19
20 #include <memory>
21 #include <ostream>
22 #include <string_view>
23 #include <string>
24 #include <tuple>
25 #include <vector>
26 #include <variant>
27
28 #include "base/macros.h"
29 #include "base/indenter.h"
30 #include "base/malloc_arena_pool.h"
31 #include "base/scoped_arena_allocator.h"
32 #include "builder.h"
33 #include "common_compiler_test.h"
34 #include "dex/code_item_accessors-inl.h"
35 #include "dex/dex_file.h"
36 #include "dex/dex_instruction.h"
37 #include "dex/standard_dex_file.h"
38 #include "driver/dex_compilation_unit.h"
39 #include "graph_checker.h"
40 #include "gtest/gtest.h"
41 #include "handle_scope-inl.h"
42 #include "handle_scope.h"
43 #include "mirror/class_loader.h"
44 #include "mirror/dex_cache.h"
45 #include "nodes.h"
46 #include "scoped_thread_state_change.h"
47 #include "ssa_builder.h"
48 #include "ssa_liveness_analysis.h"
49
50 namespace art HIDDEN {
51
52 #define NUM_INSTRUCTIONS(...) \
53 (sizeof((uint16_t[]) {__VA_ARGS__}) /sizeof(uint16_t))
54
55 #define N_REGISTERS_CODE_ITEM(NUM_REGS, ...) \
56 { NUM_REGS, 0, 0, 0, 0, 0, NUM_INSTRUCTIONS(__VA_ARGS__), 0, __VA_ARGS__ }
57
58 #define ZERO_REGISTER_CODE_ITEM(...) N_REGISTERS_CODE_ITEM(0, __VA_ARGS__)
59 #define ONE_REGISTER_CODE_ITEM(...) N_REGISTERS_CODE_ITEM(1, __VA_ARGS__)
60 #define TWO_REGISTERS_CODE_ITEM(...) N_REGISTERS_CODE_ITEM(2, __VA_ARGS__)
61 #define THREE_REGISTERS_CODE_ITEM(...) N_REGISTERS_CODE_ITEM(3, __VA_ARGS__)
62 #define FOUR_REGISTERS_CODE_ITEM(...) N_REGISTERS_CODE_ITEM(4, __VA_ARGS__)
63 #define FIVE_REGISTERS_CODE_ITEM(...) N_REGISTERS_CODE_ITEM(5, __VA_ARGS__)
64 #define SIX_REGISTERS_CODE_ITEM(...) N_REGISTERS_CODE_ITEM(6, __VA_ARGS__)
65
66 struct InstructionDumper {
67 public:
68 HInstruction* ins_;
69 };
70
71 inline bool operator==(const InstructionDumper& a, const InstructionDumper& b) {
72 return a.ins_ == b.ins_;
73 }
74 inline bool operator!=(const InstructionDumper& a, const InstructionDumper& b) {
75 return !(a == b);
76 }
77
78 inline std::ostream& operator<<(std::ostream& os, const InstructionDumper& id) {
79 if (id.ins_ == nullptr) {
80 return os << "NULL";
81 } else {
82 return os << "(" << id.ins_ << "): " << id.ins_->DumpWithArgs();
83 }
84 }
85
86 #define EXPECT_INS_EQ(a, b) EXPECT_EQ(InstructionDumper{a}, InstructionDumper{b})
87 #define EXPECT_INS_REMOVED(a) EXPECT_TRUE(IsRemoved(a)) << "Not removed: " << (InstructionDumper{a})
88 #define EXPECT_INS_RETAINED(a) EXPECT_FALSE(IsRemoved(a)) << "Removed: " << (InstructionDumper{a})
89 #define ASSERT_INS_EQ(a, b) ASSERT_EQ(InstructionDumper{a}, InstructionDumper{b})
90 #define ASSERT_INS_REMOVED(a) ASSERT_TRUE(IsRemoved(a)) << "Not removed: " << (InstructionDumper{a})
91 #define ASSERT_INS_RETAINED(a) ASSERT_FALSE(IsRemoved(a)) << "Removed: " << (InstructionDumper{a})
92
93 inline LiveInterval* BuildInterval(const size_t ranges[][2],
94 size_t number_of_ranges,
95 ScopedArenaAllocator* allocator,
96 int reg = -1,
97 HInstruction* defined_by = nullptr) {
98 LiveInterval* interval =
99 LiveInterval::MakeInterval(allocator, DataType::Type::kInt32, defined_by);
100 if (defined_by != nullptr) {
101 defined_by->SetLiveInterval(interval);
102 }
103 for (size_t i = number_of_ranges; i > 0; --i) {
104 interval->AddRange(ranges[i - 1][0], ranges[i - 1][1]);
105 }
106 interval->SetRegister(reg);
107 return interval;
108 }
109
RemoveSuspendChecks(HGraph * graph)110 inline void RemoveSuspendChecks(HGraph* graph) {
111 for (HBasicBlock* block : graph->GetBlocks()) {
112 if (block != nullptr) {
113 if (block->GetLoopInformation() != nullptr) {
114 block->GetLoopInformation()->SetSuspendCheck(nullptr);
115 }
116 for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
117 HInstruction* current = it.Current();
118 if (current->IsSuspendCheck()) {
119 current->GetBlock()->RemoveInstruction(current);
120 }
121 }
122 }
123 }
124 }
125
126 class ArenaPoolAndAllocator {
127 public:
ArenaPoolAndAllocator()128 ArenaPoolAndAllocator()
129 : pool_(), allocator_(&pool_), arena_stack_(&pool_), scoped_allocator_(&arena_stack_) { }
130
GetAllocator()131 ArenaAllocator* GetAllocator() { return &allocator_; }
GetArenaStack()132 ArenaStack* GetArenaStack() { return &arena_stack_; }
GetScopedAllocator()133 ScopedArenaAllocator* GetScopedAllocator() { return &scoped_allocator_; }
134
135 private:
136 MallocArenaPool pool_;
137 ArenaAllocator allocator_;
138 ArenaStack arena_stack_;
139 ScopedArenaAllocator scoped_allocator_;
140 };
141
142 class AdjacencyListGraph {
143 public:
144 using Edge = std::pair<const std::string_view, const std::string_view>;
AdjacencyListGraph(HGraph * graph,ArenaAllocator * alloc,const std::string_view entry_name,const std::string_view exit_name,const std::vector<Edge> & adj)145 AdjacencyListGraph(
146 HGraph* graph,
147 ArenaAllocator* alloc,
148 const std::string_view entry_name,
149 const std::string_view exit_name,
150 const std::vector<Edge>& adj) : graph_(graph) {
151 auto create_block = [&]() {
152 HBasicBlock* blk = new (alloc) HBasicBlock(graph_);
153 graph_->AddBlock(blk);
154 return blk;
155 };
156 HBasicBlock* entry = create_block();
157 HBasicBlock* exit = create_block();
158 graph_->SetEntryBlock(entry);
159 graph_->SetExitBlock(exit);
160 name_to_block_.Put(entry_name, entry);
161 name_to_block_.Put(exit_name, exit);
162 for (const auto& [src, dest] : adj) {
163 HBasicBlock* src_blk = name_to_block_.GetOrCreate(src, create_block);
164 HBasicBlock* dest_blk = name_to_block_.GetOrCreate(dest, create_block);
165 src_blk->AddSuccessor(dest_blk);
166 }
167 graph_->ComputeDominanceInformation();
168 for (auto [name, blk] : name_to_block_) {
169 block_to_name_.Put(blk, name);
170 }
171 }
172
HasBlock(const HBasicBlock * blk)173 bool HasBlock(const HBasicBlock* blk) const {
174 return block_to_name_.find(blk) != block_to_name_.end();
175 }
176
GetName(const HBasicBlock * blk)177 std::string_view GetName(const HBasicBlock* blk) const {
178 return block_to_name_.Get(blk);
179 }
180
Get(const std::string_view & sv)181 HBasicBlock* Get(const std::string_view& sv) const {
182 return name_to_block_.Get(sv);
183 }
184
185 AdjacencyListGraph(AdjacencyListGraph&&) = default;
186 AdjacencyListGraph(const AdjacencyListGraph&) = default;
187 AdjacencyListGraph& operator=(AdjacencyListGraph&&) = default;
188 AdjacencyListGraph& operator=(const AdjacencyListGraph&) = default;
189
Dump(std::ostream & os)190 std::ostream& Dump(std::ostream& os) const {
191 struct Namer : public BlockNamer {
192 public:
193 explicit Namer(const AdjacencyListGraph& alg) : BlockNamer(), alg_(alg) {}
194 std::ostream& PrintName(std::ostream& os, HBasicBlock* blk) const override {
195 if (alg_.HasBlock(blk)) {
196 return os << alg_.GetName(blk) << " (" << blk->GetBlockId() << ")";
197 } else {
198 return os << "<Unnamed B" << blk->GetBlockId() << ">";
199 }
200 }
201
202 const AdjacencyListGraph& alg_;
203 };
204 Namer namer(*this);
205 return graph_->Dump(os, /* codegen_= */ nullptr, namer);
206 }
207
208 private:
209 HGraph* graph_;
210 SafeMap<const std::string_view, HBasicBlock*> name_to_block_;
211 SafeMap<const HBasicBlock*, const std::string_view> block_to_name_;
212 };
213
214 // Have a separate helper so the OptimizingCFITest can inherit it without causing
215 // multiple inheritance errors from having two gtest as a parent twice.
216 class OptimizingUnitTestHelper {
217 public:
OptimizingUnitTestHelper()218 OptimizingUnitTestHelper()
219 : pool_and_allocator_(new ArenaPoolAndAllocator()),
220 graph_(nullptr),
221 entry_block_(nullptr),
222 exit_block_(nullptr) { }
223
GetAllocator()224 ArenaAllocator* GetAllocator() { return pool_and_allocator_->GetAllocator(); }
GetArenaStack()225 ArenaStack* GetArenaStack() { return pool_and_allocator_->GetArenaStack(); }
GetScopedAllocator()226 ScopedArenaAllocator* GetScopedAllocator() { return pool_and_allocator_->GetScopedAllocator(); }
227
ResetPoolAndAllocator()228 void ResetPoolAndAllocator() {
229 pool_and_allocator_.reset(new ArenaPoolAndAllocator());
230 }
231
232 HGraph* CreateGraph(VariableSizedHandleScope* handles = nullptr) {
233 ArenaAllocator* const allocator = pool_and_allocator_->GetAllocator();
234
235 // Reserve a big array of 0s so the dex file constructor can offsets from the header.
236 static constexpr size_t kDexDataSize = 4 * KB;
237 const uint8_t* dex_data = reinterpret_cast<uint8_t*>(allocator->Alloc(kDexDataSize));
238
239 // Create the dex file based on the fake data. Call the constructor so that we can use virtual
240 // functions. Don't use the arena for the StandardDexFile otherwise the dex location leaks.
241 auto container =
242 std::make_shared<MemoryDexFileContainer>(dex_data, sizeof(StandardDexFile::Header));
243 dex_files_.emplace_back(new StandardDexFile(dex_data,
244 "no_location",
245 /*location_checksum*/ 0,
246 /*oat_dex_file*/ nullptr,
247 std::move(container)));
248
249 graph_ = new (allocator) HGraph(
250 allocator,
251 pool_and_allocator_->GetArenaStack(),
252 handles,
253 *dex_files_.back(),
254 /*method_idx*/-1,
255 kRuntimeISA);
256 return graph_;
257 }
258
259 // Create a control-flow graph from Dex instructions.
260 HGraph* CreateCFG(const std::vector<uint16_t>& data,
261 DataType::Type return_type = DataType::Type::kInt32) {
262 ScopedObjectAccess soa(Thread::Current());
263 VariableSizedHandleScope handles(soa.Self());
264 HGraph* graph = CreateGraph(&handles);
265
266 // The code item data might not aligned to 4 bytes, copy it to ensure that.
267 const size_t code_item_size = data.size() * sizeof(data.front());
268 void* aligned_data = GetAllocator()->Alloc(code_item_size);
269 memcpy(aligned_data, &data[0], code_item_size);
270 CHECK_ALIGNED(aligned_data, StandardDexFile::CodeItem::kAlignment);
271 const dex::CodeItem* code_item = reinterpret_cast<const dex::CodeItem*>(aligned_data);
272
273 {
274 const DexCompilationUnit* dex_compilation_unit =
275 new (graph->GetAllocator()) DexCompilationUnit(
276 /* class_loader= */ Handle<mirror::ClassLoader>(), // Invalid handle.
277 /* class_linker= */ nullptr,
278 graph->GetDexFile(),
279 code_item,
280 /* class_def_idx= */ DexFile::kDexNoIndex16,
281 /* method_idx= */ dex::kDexNoIndex,
282 /* access_flags= */ 0u,
283 /* verified_method= */ nullptr,
284 /* dex_cache= */ Handle<mirror::DexCache>()); // Invalid handle.
285 CodeItemDebugInfoAccessor accessor(graph->GetDexFile(), code_item, /*dex_method_idx*/ 0u);
286 HGraphBuilder builder(graph, dex_compilation_unit, accessor, return_type);
287 bool graph_built = (builder.BuildGraph() == kAnalysisSuccess);
288 return graph_built ? graph : nullptr;
289 }
290 }
291
292 // Create simple graph with "entry", "main" and "exit" blocks, return the "main" block.
293 // Adds `HGoto` to the "entry" block and `HExit` to the "exit block. Leaves "main" block empty.
294 HBasicBlock* InitEntryMainExitGraph(VariableSizedHandleScope* handles = nullptr) {
295 CreateGraph(handles);
296 entry_block_ = AddNewBlock();
297 HBasicBlock* main_block = AddNewBlock();
298 exit_block_ = AddNewBlock();
299
300 graph_->SetEntryBlock(entry_block_);
301 graph_->SetExitBlock(exit_block_);
302
303 entry_block_->AddSuccessor(main_block);
304 main_block->AddSuccessor(exit_block_);
305
306 MakeGoto(entry_block_);
307 MakeExit(exit_block_);
308
309 return main_block;
310 }
311
312 // Creates a graph identical to `InitEntryMainExitGraph()` and adds `HReturnVoid`.
313 HBasicBlock* InitEntryMainExitGraphWithReturnVoid(VariableSizedHandleScope* handles = nullptr) {
314 HBasicBlock* return_block = InitEntryMainExitGraph(handles);
315 MakeReturnVoid(return_block);
316 return return_block;
317 }
318
319 // Insert "if_block", "then_block" and "else_block" before a given `merge_block`. Return the
320 // new blocks. Adds `HGoto` to "then_block" and "else_block". Adds `HIf` to the "if_block"
321 // if the caller provides a `condition`.
322 std::tuple<HBasicBlock*, HBasicBlock*, HBasicBlock*> CreateDiamondPattern(
323 HBasicBlock* merge_block, HInstruction* condition = nullptr) {
324 HBasicBlock* if_block = AddNewBlock();
325 HBasicBlock* then_block = AddNewBlock();
326 HBasicBlock* else_block = AddNewBlock();
327
328 HBasicBlock* predecessor = merge_block->GetSinglePredecessor();
329 predecessor->ReplaceSuccessor(merge_block, if_block);
330
331 if_block->AddSuccessor(then_block);
332 if_block->AddSuccessor(else_block);
333 then_block->AddSuccessor(merge_block);
334 else_block->AddSuccessor(merge_block);
335
336 if (condition != nullptr) {
337 MakeIf(if_block, condition);
338 }
339 MakeGoto(then_block);
340 MakeGoto(else_block);
341
342 return {if_block, then_block, else_block};
343 }
344
345 // Insert "pre-header", "loop-header" and "loop-body" blocks before a given `loop_exit` block
346 // and connect them in a `while (...) { ... }` loop pattern. Return the new blocks.
347 // Adds `HGoto` to the "pre-header" and "loop-body" blocks but leaves the "loop-header" block
348 // empty, leaving the construction of an appropriate condition and `HIf` to the caller.
349 // Note: The `loop_exit` shall be the "then" successor of the "loop-header". If the `loop_exit`
350 // is needed as the "else" successor, use `HBlock::SwapSuccessors()` to adjust the order.
CreateWhileLoop(HBasicBlock * loop_exit)351 std::tuple<HBasicBlock*, HBasicBlock*, HBasicBlock*> CreateWhileLoop(HBasicBlock* loop_exit) {
352 HBasicBlock* pre_header = AddNewBlock();
353 HBasicBlock* loop_header = AddNewBlock();
354 HBasicBlock* loop_body = AddNewBlock();
355
356 HBasicBlock* predecessor = loop_exit->GetSinglePredecessor();
357 predecessor->ReplaceSuccessor(loop_exit, pre_header);
358
359 pre_header->AddSuccessor(loop_header);
360 loop_header->AddSuccessor(loop_exit); // true successor
361 loop_header->AddSuccessor(loop_body); // false successor
362 loop_body->AddSuccessor(loop_header);
363
364 MakeGoto(pre_header);
365 MakeGoto(loop_body);
366
367 return {pre_header, loop_header, loop_body};
368 }
369
370 // Insert "pre-header" and "loop" blocks before a given `loop_exit` block and connect them in a
371 // `do { ... } while (...);` loop pattern. Return the new blocks. Adds `HGoto` to the "pre-header"
372 // block but leaves the "loop" block empty, leaving the construction of an appropriate condition
373 // and `HIf` to the caller.
374 // Note: The `loop_exit` shall be the "then" successor of the "loop". If the `loop_exit`
375 // is needed as the "else" successor, use `HBlock::SwapSuccessors()` to adjust the order.
CreateDoWhileLoop(HBasicBlock * loop_exit)376 std::tuple<HBasicBlock*, HBasicBlock*> CreateDoWhileLoop(HBasicBlock* loop_exit) {
377 HBasicBlock* pre_header = AddNewBlock();
378 HBasicBlock* loop = AddNewBlock();
379
380 HBasicBlock* predecessor = loop_exit->GetSinglePredecessor();
381 predecessor->ReplaceSuccessor(loop_exit, pre_header);
382
383 pre_header->AddSuccessor(loop);
384 loop->AddSuccessor(loop_exit); // true successor
385 loop->AddSuccessor(loop); // false successor
386
387 MakeGoto(pre_header);
388
389 return {pre_header, loop};
390 }
391
AddNewBlock()392 HBasicBlock* AddNewBlock() {
393 HBasicBlock* block = new (GetAllocator()) HBasicBlock(graph_);
394 graph_->AddBlock(block);
395 return block;
396 }
397
398 // Run GraphChecker with all checks.
399 //
400 // Return: the status whether the run is successful.
401 bool CheckGraph(std::ostream& oss = std::cerr) {
402 return CheckGraph(graph_, oss);
403 }
404
ManuallyBuildEnvFor(HInstruction * instruction,ArenaVector<HInstruction * > * current_locals)405 HEnvironment* ManuallyBuildEnvFor(HInstruction* instruction,
406 ArenaVector<HInstruction*>* current_locals) {
407 HEnvironment* environment = HEnvironment::Create(
408 GetAllocator(),
409 current_locals->size(),
410 graph_->GetArtMethod(),
411 instruction->GetDexPc(),
412 instruction);
413
414 environment->CopyFrom(ArrayRef<HInstruction* const>(*current_locals));
415 instruction->SetRawEnvironment(environment);
416 return environment;
417 }
418
EnsurePredecessorOrder(HBasicBlock * target,std::initializer_list<HBasicBlock * > preds)419 void EnsurePredecessorOrder(HBasicBlock* target, std::initializer_list<HBasicBlock*> preds) {
420 // Make sure the given preds and block predecessors have the same blocks.
421 BitVector bv(preds.size(), false, Allocator::GetCallocAllocator());
422 auto preds_and_idx = ZipCount(MakeIterationRange(target->GetPredecessors()));
423 bool correct_preds = preds.size() == target->GetPredecessors().size() &&
424 std::all_of(preds.begin(), preds.end(), [&](HBasicBlock* pred) {
425 return std::any_of(preds_and_idx.begin(),
426 preds_and_idx.end(),
427 // Make sure every target predecessor is used only
428 // once.
429 [&](std::pair<HBasicBlock*, uint32_t> cur) {
430 if (cur.first == pred && !bv.IsBitSet(cur.second)) {
431 bv.SetBit(cur.second);
432 return true;
433 } else {
434 return false;
435 }
436 });
437 }) &&
438 bv.NumSetBits() == preds.size();
439 auto dump_list = [](auto it) {
440 std::ostringstream oss;
441 oss << "[";
442 bool first = true;
443 for (HBasicBlock* b : it) {
444 if (!first) {
445 oss << ", ";
446 }
447 first = false;
448 oss << b->GetBlockId();
449 }
450 oss << "]";
451 return oss.str();
452 };
453 ASSERT_TRUE(correct_preds) << "Predecessors of " << target->GetBlockId() << " are "
454 << dump_list(target->GetPredecessors()) << " not "
455 << dump_list(preds);
456 if (correct_preds) {
457 std::copy(preds.begin(), preds.end(), target->predecessors_.begin());
458 }
459 }
460
SetupFromAdjacencyList(const std::string_view entry_name,const std::string_view exit_name,const std::vector<AdjacencyListGraph::Edge> & adj)461 AdjacencyListGraph SetupFromAdjacencyList(const std::string_view entry_name,
462 const std::string_view exit_name,
463 const std::vector<AdjacencyListGraph::Edge>& adj) {
464 return AdjacencyListGraph(graph_, GetAllocator(), entry_name, exit_name, adj);
465 }
466
ManuallyBuildEnvFor(HInstruction * ins,const std::initializer_list<HInstruction * > & env)467 void ManuallyBuildEnvFor(HInstruction* ins, const std::initializer_list<HInstruction*>& env) {
468 ArenaVector<HInstruction*> current_locals(env, GetAllocator()->Adapter(kArenaAllocInstruction));
469 OptimizingUnitTestHelper::ManuallyBuildEnvFor(ins, ¤t_locals);
470 }
471
472 HLoadClass* MakeLoadClass(HBasicBlock* block,
473 std::optional<dex::TypeIndex> ti = std::nullopt,
474 std::optional<Handle<mirror::Class>> klass = std::nullopt,
475 std::initializer_list<HInstruction*> env = {},
476 uint32_t dex_pc = kNoDexPc) {
477 HLoadClass* load_class = new (GetAllocator()) HLoadClass(
478 graph_->GetCurrentMethod(),
479 ti ? *ti : dex::TypeIndex(class_idx_++),
480 graph_->GetDexFile(),
481 /* klass= */ klass ? *klass : null_klass_,
482 /* is_referrers_class= */ false,
483 dex_pc,
484 /* needs_access_check= */ false);
485 AddOrInsertInstruction(block, load_class);
486 ManuallyBuildEnvFor(load_class, env);
487 return load_class;
488 }
489
490 HNewInstance* MakeNewInstance(HBasicBlock* block,
491 HInstruction* cls,
492 std::initializer_list<HInstruction*> env = {},
493 uint32_t dex_pc = kNoDexPc) {
494 EXPECT_TRUE(cls->IsLoadClass() || cls->IsClinitCheck()) << *cls;
495 HLoadClass* load =
496 cls->IsLoadClass() ? cls->AsLoadClass() : cls->AsClinitCheck()->GetLoadClass();
497 HNewInstance* new_instance = new (GetAllocator()) HNewInstance(
498 cls,
499 dex_pc,
500 load->GetTypeIndex(),
501 graph_->GetDexFile(),
502 /* finalizable= */ false,
503 QuickEntrypointEnum::kQuickAllocObjectInitialized);
504 AddOrInsertInstruction(block, new_instance);
505 ManuallyBuildEnvFor(new_instance, env);
506 return new_instance;
507 }
508
509 HInstanceFieldSet* MakeIFieldSet(HBasicBlock* block,
510 HInstruction* object,
511 HInstruction* data,
512 MemberOffset off,
513 uint32_t dex_pc = kNoDexPc) {
514 CHECK(data != nullptr);
515 return MakeIFieldSet(block, object, data, data->GetType(), off, dex_pc);
516 }
517
518 HInstanceFieldSet* MakeIFieldSet(HBasicBlock* block,
519 HInstruction* object,
520 HInstruction* data,
521 DataType::Type field_type,
522 MemberOffset off,
523 uint32_t dex_pc = kNoDexPc) {
524 HInstanceFieldSet* ifield_set = new (GetAllocator()) HInstanceFieldSet(
525 object,
526 data,
527 /* field= */ nullptr,
528 field_type,
529 /* field_offset= */ off,
530 /* is_volatile= */ false,
531 kUnknownFieldIndex,
532 kUnknownClassDefIndex,
533 graph_->GetDexFile(),
534 dex_pc);
535 AddOrInsertInstruction(block, ifield_set);
536 return ifield_set;
537 }
538
539 HInstanceFieldGet* MakeIFieldGet(HBasicBlock* block,
540 HInstruction* object,
541 DataType::Type type,
542 MemberOffset off,
543 uint32_t dex_pc = kNoDexPc) {
544 HInstanceFieldGet* ifield_get = new (GetAllocator()) HInstanceFieldGet(
545 object,
546 /* field= */ nullptr,
547 /* field_type= */ type,
548 /* field_offset= */ off,
549 /* is_volatile= */ false,
550 kUnknownFieldIndex,
551 kUnknownClassDefIndex,
552 graph_->GetDexFile(),
553 dex_pc);
554 AddOrInsertInstruction(block, ifield_get);
555 return ifield_get;
556 }
557
558 HNewArray* MakeNewArray(HBasicBlock* block,
559 HInstruction* cls,
560 HInstruction* length,
561 size_t component_size_shift = DataType::SizeShift(DataType::Type::kInt32),
562 std::initializer_list<HInstruction*> env = {},
563 uint32_t dex_pc = kNoDexPc) {
564 HNewArray* new_array =
565 new (GetAllocator()) HNewArray(cls, length, dex_pc, component_size_shift);
566 AddOrInsertInstruction(block, new_array);
567 ManuallyBuildEnvFor(new_array, env);
568 return new_array;
569 }
570
571 HArraySet* MakeArraySet(HBasicBlock* block,
572 HInstruction* array,
573 HInstruction* index,
574 HInstruction* value,
575 uint32_t dex_pc = kNoDexPc) {
576 CHECK(value != nullptr);
577 return MakeArraySet(block, array, index, value, value->GetType(), dex_pc);
578 }
579
580 HArraySet* MakeArraySet(HBasicBlock* block,
581 HInstruction* array,
582 HInstruction* index,
583 HInstruction* value,
584 DataType::Type type,
585 uint32_t dex_pc = kNoDexPc) {
586 HArraySet* array_set = new (GetAllocator()) HArraySet(array, index, value, type, dex_pc);
587 AddOrInsertInstruction(block, array_set);
588 return array_set;
589 }
590
591 HArrayGet* MakeArrayGet(HBasicBlock* block,
592 HInstruction* array,
593 HInstruction* index,
594 DataType::Type type,
595 uint32_t dex_pc = kNoDexPc) {
596 HArrayGet* array_get = new (GetAllocator()) HArrayGet(array, index, type, dex_pc);
597 AddOrInsertInstruction(block, array_get);
598 return array_get;
599 }
600
601 HArrayLength* MakeArrayLength(HBasicBlock* block,
602 HInstruction* array,
603 uint32_t dex_pc = kNoDexPc) {
604 HArrayLength* array_length = new (GetAllocator()) HArrayLength(array, dex_pc);
605 AddOrInsertInstruction(block, array_length);
606 return array_length;
607 }
608
609 HNullCheck* MakeNullCheck(HBasicBlock* block,
610 HInstruction* value,
611 std::initializer_list<HInstruction*> env = {},
612 uint32_t dex_pc = kNoDexPc) {
613 HNullCheck* null_check = new (GetAllocator()) HNullCheck(value, dex_pc);
614 AddOrInsertInstruction(block, null_check);
615 ManuallyBuildEnvFor(null_check, env);
616 return null_check;
617 }
618
619 HBoundsCheck* MakeBoundsCheck(HBasicBlock* block,
620 HInstruction* index,
621 HInstruction* length,
622 std::initializer_list<HInstruction*> env = {},
623 uint32_t dex_pc = kNoDexPc) {
624 HBoundsCheck* bounds_check = new (GetAllocator()) HBoundsCheck(index, length, dex_pc);
625 AddOrInsertInstruction(block, bounds_check);
626 ManuallyBuildEnvFor(bounds_check, env);
627 return bounds_check;
628 }
629
630 HVecStore* MakeVecStore(HBasicBlock* block,
631 HInstruction* base,
632 HInstruction* index,
633 HInstruction* value,
634 DataType::Type packed_type,
635 size_t vector_size_in_bytes = kDefaultTestVectorSizeInBytes,
636 uint32_t dex_pc = kNoDexPc) {
637 size_t num_of_elements = GetNumberOfElementsInVector(vector_size_in_bytes, packed_type);
638 SideEffects side_effects = SideEffects::ArrayWriteOfType(packed_type);
639 HVecStore* vec_store = new (GetAllocator()) HVecStore(
640 GetAllocator(), base, index, value, packed_type, side_effects, num_of_elements, dex_pc);
641 AddOrInsertInstruction(block, vec_store);
642 return vec_store;
643 }
644
645 HVecPredSetAll* MakeVecPredSetAll(HBasicBlock* block,
646 HInstruction* input,
647 DataType::Type packed_type,
648 size_t vector_size_in_bytes = kDefaultTestVectorSizeInBytes,
649 uint32_t dex_pc = kNoDexPc) {
650 size_t num_of_elements = GetNumberOfElementsInVector(vector_size_in_bytes, packed_type);
651 HVecPredSetAll* predicate = new (GetAllocator()) HVecPredSetAll(
652 GetAllocator(), input, packed_type, num_of_elements, dex_pc);
653 AddOrInsertInstruction(block, predicate);
654 return predicate;
655 }
656
657 HVecReplicateScalar* MakeVecReplicateScalar(
658 HBasicBlock* block,
659 HInstruction* scalar,
660 DataType::Type packed_type,
661 size_t vector_size_in_bytes = kDefaultTestVectorSizeInBytes,
662 HVecPredSetOperation* predicate = nullptr,
663 uint32_t dex_pc = kNoDexPc) {
664 size_t num_of_elements = GetNumberOfElementsInVector(vector_size_in_bytes, packed_type);
665 HVecReplicateScalar* vec_replicate_scalar = new (GetAllocator()) HVecReplicateScalar(
666 GetAllocator(), scalar, packed_type, num_of_elements, dex_pc);
667 AddOrInsertInstruction(block, vec_replicate_scalar);
668 if (predicate != nullptr) {
669 vec_replicate_scalar->SetMergingGoverningPredicate(predicate);
670 }
671 return vec_replicate_scalar;
672 }
673
674 HVecPredToBoolean* MakeVecPredToBoolean(
675 HBasicBlock* block,
676 HInstruction* input,
677 HVecPredToBoolean::PCondKind pred_cond,
678 DataType::Type packed_type,
679 size_t vector_size_in_bytes = kDefaultTestVectorSizeInBytes,
680 uint32_t dex_pc = kNoDexPc) {
681 size_t num_of_elements = GetNumberOfElementsInVector(vector_size_in_bytes, packed_type);
682 HVecPredToBoolean* vec_pred_to_boolean = new (GetAllocator()) HVecPredToBoolean(
683 GetAllocator(),
684 input,
685 pred_cond,
686 packed_type,
687 num_of_elements,
688 dex_pc);
689 AddOrInsertInstruction(block, vec_pred_to_boolean);
690 return vec_pred_to_boolean;
691 }
692
693 HVecPredWhile* MakeVecPredWhile(HBasicBlock* block,
694 HInstruction* left,
695 HInstruction* right,
696 HVecPredWhile::CondKind cond,
697 DataType::Type packed_type,
698 size_t vector_size_in_bytes = kDefaultTestVectorSizeInBytes,
699 uint32_t dex_pc = kNoDexPc) {
700 size_t num_of_elements = GetNumberOfElementsInVector(vector_size_in_bytes, packed_type);
701 HVecPredWhile* vec_pred_while = new (GetAllocator()) HVecPredWhile(
702 GetAllocator(),
703 left,
704 right,
705 cond,
706 packed_type,
707 num_of_elements,
708 dex_pc);
709 AddOrInsertInstruction(block, vec_pred_while);
710 return vec_pred_while;
711 }
712
713 HInvokeStaticOrDirect* MakeInvokeStatic(HBasicBlock* block,
714 DataType::Type return_type,
715 const std::vector<HInstruction*>& args,
716 std::initializer_list<HInstruction*> env = {},
717 uint32_t dex_pc = kNoDexPc) {
718 MethodReference method_reference{/* file= */ &graph_->GetDexFile(), /* index= */ method_idx_++};
719 size_t num_64bit_args = std::count_if(args.begin(), args.end(), [](HInstruction* insn) {
720 return DataType::Is64BitType(insn->GetType());
721 });
722 HInvokeStaticOrDirect* invoke = new (GetAllocator())
723 HInvokeStaticOrDirect(GetAllocator(),
724 args.size(),
725 /* number_of_out_vregs= */ args.size() + num_64bit_args,
726 return_type,
727 dex_pc,
728 method_reference,
729 /* resolved_method= */ nullptr,
730 HInvokeStaticOrDirect::DispatchInfo{},
731 InvokeType::kStatic,
732 /* resolved_method_reference= */ method_reference,
733 HInvokeStaticOrDirect::ClinitCheckRequirement::kNone,
734 !graph_->IsDebuggable());
735 for (auto [ins, idx] : ZipCount(MakeIterationRange(args))) {
736 invoke->SetRawInputAt(idx, ins);
737 }
738 AddOrInsertInstruction(block, invoke);
739 ManuallyBuildEnvFor(invoke, env);
740 return invoke;
741 }
742
743 template <typename Type>
744 Type* MakeBinOp(HBasicBlock* block,
745 DataType::Type result_type,
746 HInstruction* left,
747 HInstruction* right,
748 uint32_t dex_pc = kNoDexPc) {
749 static_assert(std::is_base_of_v<HBinaryOperation, Type>);
750 Type* insn = new (GetAllocator()) Type(result_type, left, right, dex_pc);
751 AddOrInsertInstruction(block, insn);
752 return insn;
753 }
754
755 HCondition* MakeCondition(HBasicBlock* block,
756 IfCondition cond,
757 HInstruction* first,
758 HInstruction* second,
759 uint32_t dex_pc = kNoDexPc) {
760 HCondition* condition = HCondition::Create(graph_, cond, first, second, dex_pc);
761 AddOrInsertInstruction(block, condition);
762 return condition;
763 }
764
765 HVecCondition* MakeVecCondition(HBasicBlock* block,
766 IfCondition cond,
767 HInstruction* first,
768 HInstruction* second,
769 DataType::Type packed_type,
770 size_t vector_size_in_bytes = kDefaultTestVectorSizeInBytes,
771 HVecPredSetOperation* predicate = nullptr,
772 uint32_t dex_pc = kNoDexPc) {
773 size_t num_of_elements = GetNumberOfElementsInVector(vector_size_in_bytes, packed_type);
774 HVecCondition* condition = HVecCondition::Create(graph_,
775 cond,
776 first,
777 second,
778 packed_type,
779 num_of_elements,
780 dex_pc);
781 AddOrInsertInstruction(block, condition);
782 if (predicate != nullptr) {
783 condition->SetMergingGoverningPredicate(predicate);
784 }
785 return condition;
786 }
787
788 HSelect* MakeSelect(HBasicBlock* block,
789 HInstruction* condition,
790 HInstruction* true_value,
791 HInstruction* false_value,
792 uint32_t dex_pc = kNoDexPc) {
793 HSelect* select = new (GetAllocator()) HSelect(condition, true_value, false_value, dex_pc);
794 AddOrInsertInstruction(block, select);
795 return select;
796 }
797
798 HSuspendCheck* MakeSuspendCheck(HBasicBlock* block,
799 std::initializer_list<HInstruction*> env = {},
800 uint32_t dex_pc = kNoDexPc) {
801 HSuspendCheck* suspend_check = new (GetAllocator()) HSuspendCheck(dex_pc);
802 AddOrInsertInstruction(block, suspend_check);
803 ManuallyBuildEnvFor(suspend_check, env);
804 return suspend_check;
805 }
806
AddOrInsertInstruction(HBasicBlock * block,HInstruction * instruction)807 void AddOrInsertInstruction(HBasicBlock* block, HInstruction* instruction) {
808 CHECK(!instruction->IsControlFlow());
809 if (block->GetLastInstruction() != nullptr && block->GetLastInstruction()->IsControlFlow()) {
810 block->InsertInstructionBefore(instruction, block->GetLastInstruction());
811 } else {
812 block->AddInstruction(instruction);
813 }
814 }
815
816 HIf* MakeIf(HBasicBlock* block, HInstruction* cond, uint32_t dex_pc = kNoDexPc) {
817 HIf* if_insn = new (GetAllocator()) HIf(cond, dex_pc);
818 block->AddInstruction(if_insn);
819 return if_insn;
820 }
821
822 HGoto* MakeGoto(HBasicBlock* block, uint32_t dex_pc = kNoDexPc) {
823 HGoto* goto_insn = new (GetAllocator()) HGoto(dex_pc);
824 block->AddInstruction(goto_insn);
825 return goto_insn;
826 }
827
828 HReturnVoid* MakeReturnVoid(HBasicBlock* block, uint32_t dex_pc = kNoDexPc) {
829 HReturnVoid* return_void = new (GetAllocator()) HReturnVoid(dex_pc);
830 block->AddInstruction(return_void);
831 return return_void;
832 }
833
834 HReturn* MakeReturn(HBasicBlock* block, HInstruction* value, uint32_t dex_pc = kNoDexPc) {
835 HReturn* return_insn = new (GetAllocator()) HReturn(value, dex_pc);
836 block->AddInstruction(return_insn);
837 return return_insn;
838 }
839
MakeExit(HBasicBlock * exit_block)840 HExit* MakeExit(HBasicBlock* exit_block) {
841 HExit* exit = new (GetAllocator()) HExit();
842 exit_block->AddInstruction(exit);
843 return exit;
844 }
845
MakePhi(HBasicBlock * block,const std::vector<HInstruction * > & ins)846 HPhi* MakePhi(HBasicBlock* block, const std::vector<HInstruction*>& ins) {
847 EXPECT_GE(ins.size(), 2u) << "Phi requires at least 2 inputs";
848 DataType::Type type = DataType::Kind(ins[0]->GetType());
849 HPhi* phi = new (GetAllocator()) HPhi(GetAllocator(), kNoRegNumber, ins.size(), type);
850 for (auto [i, idx] : ZipCount(MakeIterationRange(ins))) {
851 phi->SetRawInputAt(idx, i);
852 }
853 block->AddPhi(phi);
854 return phi;
855 }
856
MakeLinearLoopVar(HBasicBlock * header,HBasicBlock * body,int32_t initial,int32_t increment)857 std::tuple<HPhi*, HAdd*> MakeLinearLoopVar(HBasicBlock* header,
858 HBasicBlock* body,
859 int32_t initial,
860 int32_t increment) {
861 HInstruction* initial_const = graph_->GetIntConstant(initial);
862 HInstruction* increment_const = graph_->GetIntConstant(increment);
863 return MakeLinearLoopVar(header, body, initial_const, increment_const);
864 }
865
MakeLinearLoopVar(HBasicBlock * header,HBasicBlock * body,HInstruction * initial,HInstruction * increment)866 std::tuple<HPhi*, HAdd*> MakeLinearLoopVar(HBasicBlock* header,
867 HBasicBlock* body,
868 HInstruction* initial,
869 HInstruction* increment) {
870 HPhi* phi = MakePhi(header, {initial, /* placeholder */ initial});
871 HAdd* add = MakeBinOp<HAdd>(body, phi->GetType(), phi, increment);
872 phi->ReplaceInput(add, 1u); // Update back-edge input.
873 return {phi, add};
874 }
875
DefaultTypeIndexForType(DataType::Type type)876 dex::TypeIndex DefaultTypeIndexForType(DataType::Type type) {
877 switch (type) {
878 case DataType::Type::kBool:
879 return dex::TypeIndex(1);
880 case DataType::Type::kUint8:
881 case DataType::Type::kInt8:
882 return dex::TypeIndex(2);
883 case DataType::Type::kUint16:
884 case DataType::Type::kInt16:
885 return dex::TypeIndex(3);
886 case DataType::Type::kUint32:
887 case DataType::Type::kInt32:
888 return dex::TypeIndex(4);
889 case DataType::Type::kUint64:
890 case DataType::Type::kInt64:
891 return dex::TypeIndex(5);
892 case DataType::Type::kReference:
893 return dex::TypeIndex(6);
894 case DataType::Type::kFloat32:
895 return dex::TypeIndex(7);
896 case DataType::Type::kFloat64:
897 return dex::TypeIndex(8);
898 case DataType::Type::kVoid:
899 EXPECT_TRUE(false) << "No type for void!";
900 return dex::TypeIndex(1000);
901 }
902 }
903
904 // Creates a parameter. The instruction is automatically added to the entry-block.
905 HParameterValue* MakeParam(DataType::Type type, std::optional<dex::TypeIndex> ti = std::nullopt) {
906 HParameterValue* val = new (GetAllocator()) HParameterValue(
907 graph_->GetDexFile(), ti ? *ti : DefaultTypeIndexForType(type), param_count_++, type);
908 AddOrInsertInstruction(graph_->GetEntryBlock(), val);
909 return val;
910 }
911
912 protected:
CheckGraph(HGraph * graph,std::ostream & oss)913 bool CheckGraph(HGraph* graph, std::ostream& oss) {
914 GraphChecker checker(graph);
915 checker.Run();
916 checker.Dump(oss);
917 return checker.IsValid();
918 }
919
920 std::vector<std::unique_ptr<const StandardDexFile>> dex_files_;
921 std::unique_ptr<ArenaPoolAndAllocator> pool_and_allocator_;
922
923 HGraph* graph_;
924 HBasicBlock* entry_block_;
925 HBasicBlock* exit_block_;
926
927 size_t param_count_ = 0;
928 size_t class_idx_ = 42;
929 uint32_t method_idx_ = 100;
930
931 // The default size of vectors to use for tests, in bytes. 16 bytes (128 bits) is used as it is
932 // commonly the smallest size of vector used in vector extensions.
933 static constexpr size_t kDefaultTestVectorSizeInBytes = 16;
934
935 ScopedNullHandle<mirror::Class> null_klass_;
936 };
937
938 class OptimizingUnitTest : public CommonArtTest, public OptimizingUnitTestHelper {};
939
940 // Naive string diff data type.
941 using diff_t = std::list<std::pair<std::string, std::string>>;
942
943 // An alias for the empty string used to make it clear that a line is
944 // removed in a diff.
945 static const std::string removed = ""; // NOLINT [runtime/string] [4]
946
947 // Naive patch command: apply a diff to a string.
Patch(const std::string & original,const diff_t & diff)948 inline std::string Patch(const std::string& original, const diff_t& diff) {
949 std::string result = original;
950 for (const auto& p : diff) {
951 std::string::size_type pos = result.find(p.first);
952 DCHECK_NE(pos, std::string::npos)
953 << "Could not find: \"" << p.first << "\" in \"" << result << "\"";
954 result.replace(pos, p.first.size(), p.second);
955 }
956 return result;
957 }
958
959 // Returns if the instruction is removed from the graph.
IsRemoved(HInstruction * instruction)960 inline bool IsRemoved(HInstruction* instruction) {
961 return instruction->GetBlock() == nullptr;
962 }
963
964 inline std::ostream& operator<<(std::ostream& oss, const AdjacencyListGraph& alg) {
965 return alg.Dump(oss);
966 }
967
968 class PatternMatchGraphVisitor final : public HGraphVisitor {
969 private:
970 struct HandlerWrapper {
971 public:
~HandlerWrapperHandlerWrapper972 virtual ~HandlerWrapper() {}
973 virtual void operator()(HInstruction* h) = 0;
974 };
975
976 template <HInstruction::InstructionKind kKind, typename F>
977 struct KindWrapper;
978
979 #define GEN_HANDLER(nm, unused) \
980 template <typename F> \
981 struct KindWrapper<HInstruction::InstructionKind::k##nm, F> : public HandlerWrapper { \
982 public: \
983 explicit KindWrapper(F f) : f_(f) {} \
984 void operator()(HInstruction* h) override { \
985 if constexpr (std::is_invocable_v<F, H##nm*>) { \
986 f_(h->As##nm()); \
987 } else { \
988 LOG(FATAL) << "Incorrect call with " << #nm; \
989 } \
990 } \
991 \
992 private: \
993 F f_; \
994 };
995
FOR_EACH_CONCRETE_INSTRUCTION(GEN_HANDLER)996 FOR_EACH_CONCRETE_INSTRUCTION(GEN_HANDLER)
997 #undef GEN_HANDLER
998
999 template <typename F>
1000 std::unique_ptr<HandlerWrapper> GetWrapper(HInstruction::InstructionKind kind, F f) {
1001 switch (kind) {
1002 #define GEN_GETTER(nm, unused) \
1003 case HInstruction::InstructionKind::k##nm: \
1004 return std::unique_ptr<HandlerWrapper>( \
1005 new KindWrapper<HInstruction::InstructionKind::k##nm, F>(f));
1006 FOR_EACH_CONCRETE_INSTRUCTION(GEN_GETTER)
1007 #undef GEN_GETTER
1008 default:
1009 LOG(FATAL) << "Unable to handle kind " << kind;
1010 return nullptr;
1011 }
1012 }
1013
1014 public:
1015 template <typename... Inst>
PatternMatchGraphVisitor(HGraph * graph,Inst...handlers)1016 explicit PatternMatchGraphVisitor(HGraph* graph, Inst... handlers) : HGraphVisitor(graph) {
1017 FillHandlers(handlers...);
1018 }
1019
VisitInstruction(HInstruction * instruction)1020 void VisitInstruction(HInstruction* instruction) override {
1021 auto& h = handlers_[instruction->GetKind()];
1022 if (h.get() != nullptr) {
1023 (*h)(instruction);
1024 }
1025 }
1026
1027 private:
1028 template <typename Func>
GetKind()1029 constexpr HInstruction::InstructionKind GetKind() {
1030 #define CHECK_INST(nm, unused) \
1031 if constexpr (std::is_invocable_v<Func, H##nm*>) { \
1032 return HInstruction::InstructionKind::k##nm; \
1033 }
1034 FOR_EACH_CONCRETE_INSTRUCTION(CHECK_INST);
1035 #undef CHECK_INST
1036 static_assert(!std::is_invocable_v<Func, HInstruction*>,
1037 "Use on generic HInstruction not allowed");
1038 #define STATIC_ASSERT_ABSTRACT(nm, unused) && !std::is_invocable_v<Func, H##nm*>
1039 static_assert(true FOR_EACH_ABSTRACT_INSTRUCTION(STATIC_ASSERT_ABSTRACT),
1040 "Must not be abstract instruction");
1041 #undef STATIC_ASSERT_ABSTRACT
1042 #define STATIC_ASSERT_CONCRETE(nm, unused) || std::is_invocable_v<Func, H##nm*>
1043 static_assert(false FOR_EACH_CONCRETE_INSTRUCTION(STATIC_ASSERT_CONCRETE),
1044 "Must be a concrete instruction");
1045 #undef STATIC_ASSERT_CONCRETE
1046 return HInstruction::InstructionKind::kLastInstructionKind;
1047 }
1048 template <typename First>
FillHandlers(First h1)1049 void FillHandlers(First h1) {
1050 HInstruction::InstructionKind type = GetKind<First>();
1051 CHECK_NE(type, HInstruction::kLastInstructionKind)
1052 << "Unknown instruction kind. Only concrete ones please.";
1053 handlers_[type] = GetWrapper(type, h1);
1054 }
1055
1056 template <typename First, typename... Inst>
FillHandlers(First h1,Inst...handlers)1057 void FillHandlers(First h1, Inst... handlers) {
1058 FillHandlers(h1);
1059 FillHandlers<Inst...>(handlers...);
1060 }
1061
1062 std::array<std::unique_ptr<HandlerWrapper>, HInstruction::InstructionKind::kLastInstructionKind>
1063 handlers_;
1064 };
1065
1066 template <typename... Target>
1067 std::tuple<std::vector<Target*>...> FindAllInstructions(
1068 HGraph* graph,
1069 std::variant<std::nullopt_t, HBasicBlock*, std::initializer_list<HBasicBlock*>> blks =
1070 std::nullopt) {
1071 std::tuple<std::vector<Target*>...> res;
1072 PatternMatchGraphVisitor vis(
1073 graph, [&](Target* t) { std::get<std::vector<Target*>>(res).push_back(t); }...);
1074
1075 if (std::holds_alternative<std::initializer_list<HBasicBlock*>>(blks)) {
1076 for (HBasicBlock* blk : std::get<std::initializer_list<HBasicBlock*>>(blks)) {
1077 vis.VisitBasicBlock(blk);
1078 }
1079 } else if (std::holds_alternative<std::nullopt_t>(blks)) {
1080 vis.VisitInsertionOrder();
1081 } else {
1082 vis.VisitBasicBlock(std::get<HBasicBlock*>(blks));
1083 }
1084 return res;
1085 }
1086
1087 template <typename... Target>
1088 std::tuple<Target*...> FindSingleInstructions(
1089 HGraph* graph,
1090 std::variant<std::nullopt_t, HBasicBlock*, std::initializer_list<HBasicBlock*>> blks =
1091 std::nullopt) {
1092 std::tuple<Target*...> res;
1093 PatternMatchGraphVisitor vis(graph, [&](Target* t) {
1094 EXPECT_EQ(std::get<Target*>(res), nullptr)
1095 << *std::get<Target*>(res) << " already found but found " << *t << "!";
1096 std::get<Target*>(res) = t;
1097 }...);
1098 if (std::holds_alternative<std::initializer_list<HBasicBlock*>>(blks)) {
1099 for (HBasicBlock* blk : std::get<std::initializer_list<HBasicBlock*>>(blks)) {
1100 vis.VisitBasicBlock(blk);
1101 }
1102 } else if (std::holds_alternative<std::nullopt_t>(blks)) {
1103 vis.VisitInsertionOrder();
1104 } else {
1105 vis.VisitBasicBlock(std::get<HBasicBlock*>(blks));
1106 }
1107 return res;
1108 }
1109
1110 template <typename Target>
1111 Target* FindSingleInstruction(
1112 HGraph* graph,
1113 std::variant<std::nullopt_t, HBasicBlock*, std::initializer_list<HBasicBlock*>> blks =
1114 std::nullopt) {
1115 return std::get<Target*>(FindSingleInstructions<Target>(graph, blks));
1116 }
1117
1118 } // namespace art
1119
1120 #endif // ART_COMPILER_OPTIMIZING_OPTIMIZING_UNIT_TEST_H_
1121