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#include "base/arena_allocator.h"
18#include "builder.h"
19#include "dex_file.h"
20#include "dex_instruction.h"
21#include "nodes.h"
22#include "optimizing_unit_test.h"
23#include "ssa_liveness_analysis.h"
24#include "pretty_printer.h"
25
26#include "gtest/gtest.h"
27
28namespace art {
29
30class FindLoopsTest : public CommonCompilerTest {};
31
32TEST_F(FindLoopsTest, CFG1) {
33  // Constant is not used.
34  const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
35    Instruction::CONST_4 | 0 | 0,
36    Instruction::RETURN_VOID);
37
38  ArenaPool arena;
39  ArenaAllocator allocator(&arena);
40  HGraph* graph = CreateCFG(&allocator, data);
41  for (HBasicBlock* block : graph->GetBlocks()) {
42    ASSERT_EQ(block->GetLoopInformation(), nullptr);
43  }
44}
45
46TEST_F(FindLoopsTest, CFG2) {
47  const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
48    Instruction::CONST_4 | 0 | 0,
49    Instruction::RETURN);
50
51  ArenaPool arena;
52  ArenaAllocator allocator(&arena);
53  HGraph* graph = CreateCFG(&allocator, data);
54  for (HBasicBlock* block : graph->GetBlocks()) {
55    ASSERT_EQ(block->GetLoopInformation(), nullptr);
56  }
57}
58
59TEST_F(FindLoopsTest, CFG3) {
60  const uint16_t data[] = TWO_REGISTERS_CODE_ITEM(
61    Instruction::CONST_4 | 3 << 12 | 0,
62    Instruction::CONST_4 | 4 << 12 | 1 << 8,
63    Instruction::ADD_INT_2ADDR | 1 << 12,
64    Instruction::GOTO | 0x100,
65    Instruction::RETURN);
66
67  ArenaPool arena;
68  ArenaAllocator allocator(&arena);
69  HGraph* graph = CreateCFG(&allocator, data);
70  for (HBasicBlock* block : graph->GetBlocks()) {
71    ASSERT_EQ(block->GetLoopInformation(), nullptr);
72  }
73}
74
75TEST_F(FindLoopsTest, CFG4) {
76  const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
77    Instruction::CONST_4 | 0 | 0,
78    Instruction::IF_EQ, 4,
79    Instruction::CONST_4 | 4 << 12 | 0,
80    Instruction::GOTO | 0x200,
81    Instruction::CONST_4 | 5 << 12 | 0,
82    Instruction::RETURN | 0 << 8);
83
84  ArenaPool arena;
85  ArenaAllocator allocator(&arena);
86  HGraph* graph = CreateCFG(&allocator, data);
87  for (HBasicBlock* block : graph->GetBlocks()) {
88    ASSERT_EQ(block->GetLoopInformation(), nullptr);
89  }
90}
91
92TEST_F(FindLoopsTest, CFG5) {
93  const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
94    Instruction::CONST_4 | 0 | 0,
95    Instruction::IF_EQ, 3,
96    Instruction::CONST_4 | 4 << 12 | 0,
97    Instruction::RETURN | 0 << 8);
98
99  ArenaPool arena;
100  ArenaAllocator allocator(&arena);
101  HGraph* graph = CreateCFG(&allocator, data);
102  for (HBasicBlock* block : graph->GetBlocks()) {
103    ASSERT_EQ(block->GetLoopInformation(), nullptr);
104  }
105}
106
107static void TestBlock(HGraph* graph,
108                      uint32_t block_id,
109                      bool is_loop_header,
110                      uint32_t parent_loop_header_id,
111                      const int* blocks_in_loop = nullptr,
112                      size_t number_of_blocks = 0) {
113  HBasicBlock* block = graph->GetBlocks()[block_id];
114  ASSERT_EQ(block->IsLoopHeader(), is_loop_header);
115  if (parent_loop_header_id == kInvalidBlockId) {
116    ASSERT_EQ(block->GetLoopInformation(), nullptr);
117  } else {
118    ASSERT_EQ(block->GetLoopInformation()->GetHeader()->GetBlockId(), parent_loop_header_id);
119  }
120
121  if (blocks_in_loop != nullptr) {
122    HLoopInformation* info = block->GetLoopInformation();
123    const BitVector& blocks = info->GetBlocks();
124    ASSERT_EQ(blocks.NumSetBits(), number_of_blocks);
125    for (size_t i = 0; i < number_of_blocks; ++i) {
126      ASSERT_TRUE(blocks.IsBitSet(blocks_in_loop[i]));
127    }
128  } else {
129    ASSERT_FALSE(block->IsLoopHeader());
130  }
131}
132
133TEST_F(FindLoopsTest, Loop1) {
134  // Simple loop with one preheader and one back edge.
135  // var a = 0;
136  // while (a == a) {
137  // }
138  // return;
139  const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
140    Instruction::CONST_4 | 0 | 0,
141    Instruction::IF_EQ, 3,
142    Instruction::GOTO | 0xFE00,
143    Instruction::RETURN_VOID);
144
145  ArenaPool arena;
146  ArenaAllocator allocator(&arena);
147  HGraph* graph = CreateCFG(&allocator, data);
148
149  TestBlock(graph, 0, false, kInvalidBlockId);  // entry block
150  TestBlock(graph, 1, false, kInvalidBlockId);  // pre header
151  const int blocks2[] = {2, 3};
152  TestBlock(graph, 2, true, 2, blocks2, 2);     // loop header
153  TestBlock(graph, 3, false, 2);                // block in loop
154  TestBlock(graph, 4, false, kInvalidBlockId);  // return block
155  TestBlock(graph, 5, false, kInvalidBlockId);  // exit block
156}
157
158TEST_F(FindLoopsTest, Loop2) {
159  // Make sure we support a preheader of a loop not being the first predecessor
160  // in the predecessor list of the header.
161  // var a = 0;
162  // while (a == a) {
163  // }
164  // return a;
165  const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
166    Instruction::CONST_4 | 0 | 0,
167    Instruction::GOTO | 0x400,
168    Instruction::IF_EQ, 4,
169    Instruction::GOTO | 0xFE00,
170    Instruction::GOTO | 0xFD00,
171    Instruction::RETURN | 0 << 8);
172
173  ArenaPool arena;
174  ArenaAllocator allocator(&arena);
175  HGraph* graph = CreateCFG(&allocator, data);
176
177  TestBlock(graph, 0, false, kInvalidBlockId);  // entry block
178  TestBlock(graph, 1, false, kInvalidBlockId);  // goto block
179  const int blocks2[] = {2, 3};
180  TestBlock(graph, 2, true, 2, blocks2, 2);     // loop header
181  TestBlock(graph, 3, false, 2);                // block in loop
182  TestBlock(graph, 4, false, kInvalidBlockId);  // pre header
183  TestBlock(graph, 5, false, kInvalidBlockId);  // return block
184  TestBlock(graph, 6, false, kInvalidBlockId);  // exit block
185}
186
187TEST_F(FindLoopsTest, Loop3) {
188  // Make sure we create a preheader of a loop when a header originally has two
189  // incoming blocks and one back edge.
190  const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
191    Instruction::CONST_4 | 0 | 0,
192    Instruction::IF_EQ, 3,
193    Instruction::GOTO | 0x100,
194    Instruction::IF_EQ, 3,
195    Instruction::GOTO | 0xFE00,
196    Instruction::RETURN | 0 << 8);
197
198  ArenaPool arena;
199  ArenaAllocator allocator(&arena);
200  HGraph* graph = CreateCFG(&allocator, data);
201
202  TestBlock(graph, 0, false, kInvalidBlockId);  // entry block
203  TestBlock(graph, 1, false, kInvalidBlockId);  // goto block
204  TestBlock(graph, 2, false, kInvalidBlockId);
205  const int blocks2[] = {3, 4};
206  TestBlock(graph, 3, true, 3, blocks2, 2);     // loop header
207  TestBlock(graph, 4, false, 3);                // block in loop
208  TestBlock(graph, 5, false, kInvalidBlockId);  // pre header
209  TestBlock(graph, 6, false, kInvalidBlockId);  // return block
210  TestBlock(graph, 7, false, kInvalidBlockId);  // exit block
211  TestBlock(graph, 8, false, kInvalidBlockId);  // synthesized pre header
212}
213
214TEST_F(FindLoopsTest, Loop4) {
215  // Test loop with originally two back edges.
216  const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
217    Instruction::CONST_4 | 0 | 0,
218    Instruction::IF_EQ, 6,
219    Instruction::IF_EQ, 3,
220    Instruction::GOTO | 0xFC00,
221    Instruction::GOTO | 0xFB00,
222    Instruction::RETURN | 0 << 8);
223
224  ArenaPool arena;
225  ArenaAllocator allocator(&arena);
226  HGraph* graph = CreateCFG(&allocator, data);
227
228  TestBlock(graph, 0, false, kInvalidBlockId);  // entry block
229  TestBlock(graph, 1, false, kInvalidBlockId);  // pre header
230  const int blocks2[] = {2, 3, 4, 5};
231  TestBlock(graph, 2, true, 2, blocks2, arraysize(blocks2));  // loop header
232  TestBlock(graph, 3, false, 2);                // block in loop
233  TestBlock(graph, 4, false, 2);                // back edge
234  TestBlock(graph, 5, false, 2);                // back edge
235  TestBlock(graph, 6, false, kInvalidBlockId);  // return block
236  TestBlock(graph, 7, false, kInvalidBlockId);  // exit block
237}
238
239
240TEST_F(FindLoopsTest, Loop5) {
241  // Test loop with two exit edges.
242  const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
243    Instruction::CONST_4 | 0 | 0,
244    Instruction::IF_EQ, 6,
245    Instruction::IF_EQ, 3,
246    Instruction::GOTO | 0x0200,
247    Instruction::GOTO | 0xFB00,
248    Instruction::RETURN | 0 << 8);
249
250  ArenaPool arena;
251  ArenaAllocator allocator(&arena);
252  HGraph* graph = CreateCFG(&allocator, data);
253
254  TestBlock(graph, 0, false, kInvalidBlockId);  // entry block
255  TestBlock(graph, 1, false, kInvalidBlockId);  // pre header
256  const int blocks2[] = {2, 3, 5};
257  TestBlock(graph, 2, true, 2, blocks2, 3);     // loop header
258  TestBlock(graph, 3, false, 2);                // block in loop
259  TestBlock(graph, 4, false, kInvalidBlockId);  // loop exit
260  TestBlock(graph, 5, false, 2);                // back edge
261  TestBlock(graph, 6, false, kInvalidBlockId);  // return block
262  TestBlock(graph, 7, false, kInvalidBlockId);  // exit block
263  TestBlock(graph, 8, false, kInvalidBlockId);  // synthesized block at the loop exit
264}
265
266TEST_F(FindLoopsTest, InnerLoop) {
267  const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
268    Instruction::CONST_4 | 0 | 0,
269    Instruction::IF_EQ, 6,
270    Instruction::IF_EQ, 3,
271    Instruction::GOTO | 0xFE00,  // inner loop
272    Instruction::GOTO | 0xFB00,
273    Instruction::RETURN | 0 << 8);
274
275  ArenaPool arena;
276  ArenaAllocator allocator(&arena);
277  HGraph* graph = CreateCFG(&allocator, data);
278
279  TestBlock(graph, 0, false, kInvalidBlockId);  // entry block
280  TestBlock(graph, 1, false, kInvalidBlockId);  // pre header of outer loop
281  const int blocks2[] = {2, 3, 4, 5, 8};
282  TestBlock(graph, 2, true, 2, blocks2, 5);     // outer loop header
283  const int blocks3[] = {3, 4};
284  TestBlock(graph, 3, true, 3, blocks3, 2);     // inner loop header
285  TestBlock(graph, 4, false, 3);                // back edge on inner loop
286  TestBlock(graph, 5, false, 2);                // back edge on outer loop
287  TestBlock(graph, 6, false, kInvalidBlockId);  // return block
288  TestBlock(graph, 7, false, kInvalidBlockId);  // exit block
289  TestBlock(graph, 8, false, 2);                // synthesized block as pre header of inner loop
290
291  ASSERT_TRUE(graph->GetBlocks()[3]->GetLoopInformation()->IsIn(
292                    *graph->GetBlocks()[2]->GetLoopInformation()));
293  ASSERT_FALSE(graph->GetBlocks()[2]->GetLoopInformation()->IsIn(
294                    *graph->GetBlocks()[3]->GetLoopInformation()));
295}
296
297TEST_F(FindLoopsTest, TwoLoops) {
298  const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
299    Instruction::CONST_4 | 0 | 0,
300    Instruction::IF_EQ, 3,
301    Instruction::GOTO | 0xFE00,  // first loop
302    Instruction::IF_EQ, 3,
303    Instruction::GOTO | 0xFE00,  // second loop
304    Instruction::RETURN | 0 << 8);
305
306  ArenaPool arena;
307  ArenaAllocator allocator(&arena);
308  HGraph* graph = CreateCFG(&allocator, data);
309
310  TestBlock(graph, 0, false, kInvalidBlockId);  // entry block
311  TestBlock(graph, 1, false, kInvalidBlockId);  // pre header of first loop
312  const int blocks2[] = {2, 3};
313  TestBlock(graph, 2, true, 2, blocks2, 2);     // first loop header
314  TestBlock(graph, 3, false, 2);                // back edge of first loop
315  const int blocks4[] = {4, 5};
316  TestBlock(graph, 4, true, 4, blocks4, 2);     // second loop header
317  TestBlock(graph, 5, false, 4);                // back edge of second loop
318  TestBlock(graph, 6, false, kInvalidBlockId);  // return block
319  TestBlock(graph, 7, false, kInvalidBlockId);  // exit block
320
321  ASSERT_FALSE(graph->GetBlocks()[4]->GetLoopInformation()->IsIn(
322                    *graph->GetBlocks()[2]->GetLoopInformation()));
323  ASSERT_FALSE(graph->GetBlocks()[2]->GetLoopInformation()->IsIn(
324                    *graph->GetBlocks()[4]->GetLoopInformation()));
325}
326
327TEST_F(FindLoopsTest, NonNaturalLoop) {
328  const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
329    Instruction::CONST_4 | 0 | 0,
330    Instruction::IF_EQ, 3,
331    Instruction::GOTO | 0x0100,
332    Instruction::IF_EQ, 3,
333    Instruction::GOTO | 0xFD00,
334    Instruction::RETURN | 0 << 8);
335
336  ArenaPool arena;
337  ArenaAllocator allocator(&arena);
338  HGraph* graph = CreateCFG(&allocator, data);
339  ASSERT_TRUE(graph->GetBlocks()[3]->IsLoopHeader());
340  HLoopInformation* info = graph->GetBlocks()[3]->GetLoopInformation();
341  ASSERT_EQ(1u, info->NumberOfBackEdges());
342  ASSERT_FALSE(info->GetHeader()->Dominates(info->GetBackEdges()[0]));
343}
344
345TEST_F(FindLoopsTest, DoWhileLoop) {
346  const uint16_t data[] = ONE_REGISTER_CODE_ITEM(
347    Instruction::CONST_4 | 0 | 0,
348    Instruction::GOTO | 0x0100,
349    Instruction::IF_EQ, 0xFFFF,
350    Instruction::RETURN | 0 << 8);
351
352  ArenaPool arena;
353  ArenaAllocator allocator(&arena);
354  HGraph* graph = CreateCFG(&allocator, data);
355
356  TestBlock(graph, 0, false, kInvalidBlockId);  // entry block
357  TestBlock(graph, 1, false, kInvalidBlockId);  // pre header of first loop
358  const int blocks2[] = {2, 3, 6};
359  TestBlock(graph, 2, true, 2, blocks2, 3);     // loop header
360  TestBlock(graph, 3, false, 2);                // back edge of first loop
361  TestBlock(graph, 4, false, kInvalidBlockId);  // return block
362  TestBlock(graph, 5, false, kInvalidBlockId);  // exit block
363  TestBlock(graph, 6, false, 2);                // synthesized block to avoid a critical edge
364}
365
366}  // namespace art
367