1 /*
2 * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 /*
25 * @test
26 * @modules jdk.incubator.code
27 * @run junit TestLiveness
28 */
29
30 import jdk.incubator.code.Block;
31 import jdk.incubator.code.CodeElement;
32 import jdk.incubator.code.Op;
33 import jdk.incubator.code.Value;
34 import jdk.incubator.code.analysis.Liveness;
35 import jdk.incubator.code.dialect.java.JavaOp;
36 import jdk.incubator.code.dialect.java.JavaType;
37 import jdk.incubator.code.extern.OpParser;
38 import org.junit.jupiter.api.Assertions;
39 import org.junit.jupiter.api.Test;
40
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.concurrent.atomic.AtomicInteger;
46 import java.util.stream.Collectors;
47
48 public class TestLiveness {
49
50 static final String F = """
51 func @"f" (%0 : java.type:"int", %1 : java.type:"int")java.type:"int" -> {
52 %2 : java.type:"int" = add %0 %1;
53 return %2;
54 };
55 """;
56
57 @Test
58 public void testF() {
59 Op op = OpParser.fromString(JavaOp.JAVA_DIALECT_FACTORY, F).getFirst();
60
61 var actual = liveness(op);
62 var expected = Map.of(
63 0, List.of(Set.of(), Set.of()));
64 Assertions.assertEquals(expected, actual);
65 }
66
67 static final String IF_ELSE = """
68 func @"ifelse" (%0 : java.type:"int", %1 : java.type:"int", %2 : java.type:"int")java.type:"int" -> {
69 %3 : java.type:"int" = constant @10;
70 %4 : java.type:"boolean" = lt %2 %3;
71 cbranch %4 ^block_0 ^block_1;
72
73 ^block_0:
74 %5 : java.type:"int" = constant @1;
75 %6 : java.type:"int" = add %0 %5;
76 branch ^block_2(%6, %1);
77
78 ^block_1:
79 %7 : java.type:"int" = constant @2;
80 %8 : java.type:"int" = add %1 %7;
81 branch ^block_2(%0, %8);
82
83 ^block_2(%9 : java.type:"int", %10 : java.type:"int"):
84 %11 : java.type:"int" = add %9 %10;
85 return %11;
86 };
87 """;
88
89 @Test
90 public void testIfElse() {
91 Op op = OpParser.fromString(JavaOp.JAVA_DIALECT_FACTORY, IF_ELSE).getFirst();
92
93 var actual = liveness(op);
94 var expected = Map.of(
95 0, List.of(Set.of(), Set.of(0, 1)),
96 1, List.of(Set.of(0, 1), Set.of()),
97 2, List.of(Set.of(0, 1), Set.of()),
98 3, List.of(Set.of(), Set.of())
99 );
100 Assertions.assertEquals(expected, actual);
101 }
102
103 static final String LOOP = """
104 func @"loop" (%0 : java.type:"int")java.type:"int" -> {
105 %1 : java.type:"int" = constant @0;
106 %2 : java.type:"int" = constant @0;
107 branch ^block_0(%1, %2);
108
109 ^block_0(%3 : java.type:"int", %4 : java.type:"int"):
110 %5 : java.type:"boolean" = lt %4 %0;
111 cbranch %5 ^block_1 ^block_2;
112
113 ^block_1:
114 %6 : java.type:"int" = add %3 %4;
115 branch ^block_3;
116
117 ^block_3:
118 %7 : java.type:"int" = constant @1;
119 %8 : java.type:"int" = add %4 %7;
120 branch ^block_0(%6, %8);
121
122 ^block_2:
123 return %3;
124 };
125 """;
126
127 @Test
128 public void testLoop() {
129 Op op = OpParser.fromString(JavaOp.JAVA_DIALECT_FACTORY, LOOP).getFirst();
130
131 var actual = liveness(op);
132 var expected = Map.of(
133 0, List.of(Set.of(), Set.of(0)),
134 1, List.of(Set.of(0), Set.of(0, 3, 4)),
135 2, List.of(Set.of(0, 3, 4), Set.of(0, 4, 6)),
136 3, List.of(Set.of(3), Set.of()),
137 4, List.of(Set.of(0, 4, 6), Set.of(0))
138 );
139 Assertions.assertEquals(expected, actual);
140 }
141
142 static final String IF_ELSE_NESTED = """
143 func @"ifelseNested" (%0 : java.type:"int", %1 : java.type:"int", %2 : java.type:"int", %3 : java.type:"int", %4 : java.type:"int")java.type:"int" -> {
144 %5 : java.type:"int" = constant @20;
145 %6 : java.type:"boolean" = lt %4 %5;
146 cbranch %6 ^block_0 ^block_1;
147
148 ^block_0:
149 %7 : java.type:"int" = constant @10;
150 %8 : java.type:"boolean" = lt %4 %7;
151 cbranch %8 ^block_2 ^block_3;
152
153 ^block_2:
154 %9 : java.type:"int" = constant @1;
155 %10 : java.type:"int" = add %0 %9;
156 branch ^block_4(%10, %1);
157
158 ^block_3:
159 %11 : java.type:"int" = constant @2;
160 %12 : java.type:"int" = add %1 %11;
161 branch ^block_4(%0, %12);
162
163 ^block_4(%13 : java.type:"int", %14 : java.type:"int"):
164 %15 : java.type:"int" = constant @3;
165 %16 : java.type:"int" = add %2 %15;
166 branch ^block_5(%13, %14, %16, %3);
167
168 ^block_1:
169 %17 : java.type:"int" = constant @20;
170 %18 : java.type:"boolean" = gt %4 %17;
171 cbranch %18 ^block_6 ^block_7;
172
173 ^block_6:
174 %19 : java.type:"int" = constant @4;
175 %20 : java.type:"int" = add %0 %19;
176 branch ^block_8(%20, %1);
177
178 ^block_7:
179 %21 : java.type:"int" = constant @5;
180 %22 : java.type:"int" = add %1 %21;
181 branch ^block_8(%0, %22);
182
183 ^block_8(%23 : java.type:"int", %24 : java.type:"int"):
184 %25 : java.type:"int" = constant @6;
185 %26 : java.type:"int" = add %3 %25;
186 branch ^block_5(%23, %24, %2, %26);
187
188 ^block_5(%27 : java.type:"int", %28 : java.type:"int", %29 : java.type:"int", %30 : java.type:"int"):
189 %31 : java.type:"int" = add %27 %28;
190 %32 : java.type:"int" = add %31 %29;
191 %33 : java.type:"int" = add %32 %30;
192 return %33;
193 };
194 """;
195
196 @Test
197 public void testIfElseNested() {
198 Op op = OpParser.fromString(JavaOp.JAVA_DIALECT_FACTORY, IF_ELSE_NESTED).getFirst();
199
200 var actual = liveness(op);
201 var expected = Map.of(
202 0, List.of(Set.of(), Set.of(0, 1, 2, 3, 4)),
203 1, List.of(Set.of(0, 1, 2, 3, 4), Set.of(0, 1, 2, 3)),
204 2, List.of(Set.of(0, 1, 2, 3, 4), Set.of(0, 1, 2, 3)),
205 3, List.of(Set.of(0, 1, 2, 3), Set.of(2, 3)),
206 4, List.of(Set.of(0, 1, 2, 3), Set.of(2, 3)),
207 5, List.of(Set.of(2, 3), Set.of()),
208 6, List.of(Set.of(), Set.of()),
209 7, List.of(Set.of(0, 1, 2, 3), Set.of(2, 3)),
210 8, List.of(Set.of(0, 1, 2, 3), Set.of(2, 3)),
211 9, List.of(Set.of(2, 3), Set.of())
212 );
213 Assertions.assertEquals(expected, actual);
214 }
215
216 static final String LOOP_NESTED = """
217 func @"loopNested" (%0 : java.type:"int")java.type:"int" -> {
218 %1 : java.type:"int" = constant @0;
219 %2 : java.type:"int" = constant @0;
220 branch ^block_0(%1, %2);
221
222 ^block_0(%3 : java.type:"int", %4 : java.type:"int"):
223 %5 : java.type:"boolean" = lt %4 %0;
224 cbranch %5 ^block_1 ^block_2;
225
226 ^block_1:
227 %6 : java.type:"int" = constant @0;
228 branch ^block_3(%3, %6);
229
230 ^block_3(%7 : java.type:"int", %8 : java.type:"int"):
231 %9 : java.type:"boolean" = lt %8 %0;
232 cbranch %9 ^block_4 ^block_5;
233
234 ^block_4:
235 %10 : java.type:"int" = add %7 %4;
236 %11 : java.type:"int" = add %10 %8;
237 branch ^block_6;
238
239 ^block_6:
240 %12 : java.type:"int" = constant @1;
241 %13 : java.type:"int" = add %8 %12;
242 branch ^block_3(%11, %13);
243
244 ^block_5:
245 branch ^block_7;
246
247 ^block_7:
248 %14 : java.type:"int" = constant @1;
249 %15 : java.type:"int" = add %4 %14;
250 branch ^block_0(%7, %15);
251
252 ^block_2:
253 return %3;
254 };
255 """;
256
257 @Test
258 public void testLoopNested() {
259 Op op = OpParser.fromString(JavaOp.JAVA_DIALECT_FACTORY, LOOP_NESTED).getFirst();
260
261 var actual = liveness(op);
262 var expected = Map.of(
263 0, List.of(Set.of(), Set.of(0)),
264 1, List.of(Set.of(0), Set.of(0, 3, 4)),
265 2, List.of(Set.of(0, 3, 4), Set.of(0, 4)),
266 3, List.of(Set.of(3), Set.of()),
267 4, List.of(Set.of(0, 4), Set.of(0, 4, 7, 8)),
268 5, List.of(Set.of(0, 4, 7, 8), Set.of(0, 4, 8, 11)),
269 6, List.of(Set.of(0, 4, 7), Set.of(0, 4, 7)),
270 7, List.of(Set.of(0, 4, 8, 11), Set.of(0, 4)),
271 8, List.of(Set.of(0, 4, 7), Set.of(0))
272 );
273 Assertions.assertEquals(expected, actual);
274 }
275
276 static Map<Integer, List<Set<Integer>>> liveness(Op op) {
277 Liveness l = new Liveness(op);
278 System.out.println(l);
279
280 Map<Value, Integer> valueMap = valueNameMapping(op);
281 Map<Block, Integer> blockMap = blockNameMapping(op);
282
283 return op.traverse(new HashMap<>(),
284 CodeElement.blockVisitor((m, b) -> {
285 if (b.ancestorOp() == op) {
286 Liveness.BlockInfo lbi = l.getLiveness(b);
287 m.put(blockMap.get(b),
288 List.of(
289 lbi.liveIn().stream().map(valueMap::get).collect(Collectors.toSet()),
290 lbi.liveOut().stream().map(valueMap::get).collect(Collectors.toSet())
291 ));
292 }
293 return m;
294 }));
295 }
296
297 static Map<Block, Integer> blockNameMapping(Op top) {
298 AtomicInteger i = new AtomicInteger();
299 return top.traverse(new HashMap<>(), CodeElement.blockVisitor((m, b) -> {
300 if (b.ancestorOp() != top) {
301 return m;
302 }
303
304 m.computeIfAbsent(b, _ -> i.getAndIncrement());
305 for (Block.Reference s : b.successors()) {
306 m.computeIfAbsent(s.targetBlock(), _ -> i.getAndIncrement());
307 }
308
309 return m;
310 }));
311 }
312
313 static Map<Value, Integer> valueNameMapping(Op top) {
314 AtomicInteger i = new AtomicInteger();
315 return top.traverse(new HashMap<>(), (m, e) -> {
316 switch (e) {
317 case Block b -> {
318 for (Block.Parameter p : b.parameters()) {
319 m.put(p, i.getAndIncrement());
320 }
321 }
322 case Op op -> {
323 Op.Result r = op.result();
324 if (r != null && !r.type().equals(JavaType.VOID)) {
325 m.put(r, i.getAndIncrement());
326 }
327 }
328 default -> {
329 }
330 }
331 return m;
332 });
333 }
334 }