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 import jdk.incubator.code.Block;
 25 import jdk.incubator.code.Body;
 26 import jdk.incubator.code.Op;
 27 import jdk.incubator.code.dialect.core.CoreOp;
 28 import jdk.incubator.code.dialect.core.CoreType;
 29 import jdk.incubator.code.dialect.java.JavaType;
 30 import jdk.incubator.code.extern.OpParser;
 31 import org.junit.jupiter.api.Assertions;
 32 import org.junit.jupiter.api.Test;
 33 
 34 import java.util.HashMap;
 35 import java.util.HashSet;
 36 import java.util.Map;
 37 import java.util.Set;
 38 import java.util.function.Function;
 39 import java.util.stream.Collectors;
 40 
 41 import static jdk.incubator.code.dialect.core.CoreOp.*;
 42 
 43 /*
 44  * @test
 45  * @modules jdk.incubator.code
 46  * @run junit TestDominate
 47  */
 48 
 49 public class TestDominate {
 50 
 51     @Test
 52     public void testUnmodifiableIdoms() {
 53         CoreOp.FuncOp f = func("f", CoreType.FUNCTION_TYPE_VOID).body(entry -> {
 54             Block.Builder ifBlock = entry.block();
 55             Block.Builder elseBlock = entry.block();
 56             Block.Builder end = entry.block();
 57 
 58             Op.Result p = entry.op(constant(JavaType.BOOLEAN, true));
 59             entry.op(conditionalBranch(p, ifBlock.successor(), elseBlock.successor()));
 60 
 61             ifBlock.op(branch(end.successor()));
 62 
 63             elseBlock.op(branch(end.successor()));
 64 
 65             end.op(CoreOp.return_());
 66         });
 67 
 68         Map<Block, Block> idoms = f.body().immediateDominators();
 69         Assertions.assertThrows(UnsupportedOperationException.class,
 70                 () -> idoms.put(f.body().entryBlock(), f.body().entryBlock()));
 71         Assertions.assertThrows(UnsupportedOperationException.class,
 72                 idoms::clear);
 73 
 74         Map<Block, Block> ipdoms = f.body().immediatePostDominators();
 75         Assertions.assertThrows(UnsupportedOperationException.class,
 76                 () -> ipdoms.put(f.body().entryBlock(), f.body().entryBlock()));
 77         Assertions.assertThrows(UnsupportedOperationException.class,
 78                 ipdoms::clear);
 79     }
 80 
 81     @Test
 82     public void testIfElse() {
 83         CoreOp.FuncOp f = func("f", CoreType.FUNCTION_TYPE_VOID).body(entry -> {
 84             Block.Builder ifBlock = entry.block();
 85             Block.Builder elseBlock = entry.block();
 86             Block.Builder end = entry.block();
 87 
 88             Op.Result p = entry.op(constant(JavaType.BOOLEAN, true));
 89             entry.op(conditionalBranch(p, ifBlock.successor(), elseBlock.successor()));
 90 
 91             ifBlock.op(branch(end.successor()));
 92 
 93             elseBlock.op(branch(end.successor()));
 94 
 95             end.op(CoreOp.return_());
 96         });
 97 
 98         boolean[][] bvs = new boolean[][]{
 99                 {true, false, false, false},
100                 {true, true, false, false},
101                 {true, false, true, false},
102                 {true, false, false, true}
103         };
104 
105         test(f, bvs);
106     }
107 
108     @Test
109     public void testForwardSuccessors() {
110         CoreOp.FuncOp f = func("f", CoreType.FUNCTION_TYPE_VOID).body(entry -> {
111             Block.Builder b1 = entry.block();
112             Block.Builder b2 = entry.block();
113             Block.Builder b3 = entry.block();
114             Block.Builder b4 = entry.block();
115             Block.Builder b5 = entry.block();
116 
117             Op.Result p = entry.op(constant(JavaType.BOOLEAN, true));
118             entry.op(conditionalBranch(p, b4.successor(), b2.successor()));
119 
120             b4.op(conditionalBranch(p, b5.successor(), b3.successor()));
121 
122             b2.op(conditionalBranch(p, b5.successor(), b1.successor()));
123 
124             b5.op(CoreOp.return_());
125 
126             b3.op(branch(b1.successor()));
127 
128             b1.op(CoreOp.return_());
129         });
130 
131         System.out.println(f.toText());
132         boolean[][] bvs = new boolean[][]{
133                 {true, false, false, false, false, false},
134                 {true, true, false, false, false, false},
135                 {true, true, true, false, false, false},
136                 {true, false, false, true, false, false},
137                 {true, false, false, false, true, false},
138                 {true, false, false, false, false, true},
139         };
140 
141         test(f, bvs);
142     }
143 
144     @Test
145     public void testBackbranch() {
146         CoreOp.FuncOp f = func("f", CoreType.FUNCTION_TYPE_VOID).body(entry -> {
147             Block.Builder cond = entry.block();
148             Block.Builder body = entry.block();
149             Block.Builder update = entry.block();
150             Block.Builder end = entry.block();
151 
152             Op.Result p = entry.op(constant(JavaType.BOOLEAN, true));
153             entry.op(branch(cond.successor()));
154 
155             cond.op(conditionalBranch(p, body.successor(), end.successor()));
156 
157             body.op(branch(update.successor()));
158 
159             update.op(branch(cond.successor()));
160 
161             end.op(CoreOp.return_());
162 
163         });
164 
165         boolean[][] bvs = new boolean[][]{
166                 {true, false, false, false, false},
167                 {true, true, false, false, false},
168                 {true, true, true, false, false},
169                 {true, true, true, true, false},
170                 {true, true, false, false, true},
171         };
172         test(f, bvs);
173     }
174 
175     static void test(CoreOp.FuncOp f, boolean[][] bvs) {
176         Block[] bs = f.body().blocks().toArray(Block[]::new);
177         for (int i = 0; i < bs.length; i++) {
178             for (int j = 0; j < bs.length; j++) {
179                 Block x = bs[i];
180                 Block y = bs[j];
181                 Assertions.assertEquals(bvs[j][i], y.isDominatedBy(x));
182             }
183         }
184     }
185 
186 
187     @Test
188     public void testImmediateDominators() {
189         CoreOp.FuncOp f = func("f", CoreType.FUNCTION_TYPE_VOID).body(entry -> {
190             Block.Builder b6 = entry.block();
191             Block.Builder b5 = entry.block();
192             Block.Builder b4 = entry.block();
193             Block.Builder b3 = entry.block();
194             Block.Builder b2 = entry.block();
195             Block.Builder b1 = entry.block();
196 
197             Op.Result p = entry.op(constant(JavaType.BOOLEAN, true));
198             entry.op(branch(b6.successor()));
199 
200             b6.op(conditionalBranch(p, b5.successor(), b4.successor()));
201 
202             b5.op(branch(b1.successor()));
203 
204             b4.op(conditionalBranch(p, b2.successor(), b3.successor()));
205 
206             b1.op(branch(b2.successor()));
207 
208             b2.op(conditionalBranch(p, b1.successor(), b3.successor()));
209 
210             b3.op(branch(b2.successor()));
211         });
212         System.out.println(f.toText());
213         Map<Block, Block> idoms = f.body().immediateDominators();
214         System.out.println(idoms);
215 
216         Block entry = f.body().entryBlock();
217         Block b6 = entry.successors().get(0).targetBlock();
218 
219         for (Block b : f.body().blocks()) {
220             if (b == entry || b == b6) {
221                 Assertions.assertEquals(entry, idoms.get(b));
222             } else {
223                 Assertions.assertEquals(b6, idoms.get(b));
224             }
225         }
226     }
227 
228     @Test
229     public void testCytronExample() {
230         CoreOp.FuncOp f = func("f", CoreType.FUNCTION_TYPE_VOID).body(entry -> {
231             Block.Builder exit = entry.block();
232             Block.Builder b12 = entry.block();
233             Block.Builder b11 = entry.block();
234             Block.Builder b10 = entry.block();
235             Block.Builder b9 = entry.block();
236             Block.Builder b8 = entry.block();
237             Block.Builder b7 = entry.block();
238             Block.Builder b6 = entry.block();
239             Block.Builder b5 = entry.block();
240             Block.Builder b4 = entry.block();
241             Block.Builder b3 = entry.block();
242             Block.Builder b2 = entry.block();
243             Block.Builder b1 = entry.block();
244 
245             Op.Result p = entry.op(constant(JavaType.BOOLEAN, true));
246 
247             entry.op(conditionalBranch(p, exit.successor(), b1.successor()));
248 
249             b1.op(branch(b2.successor()));
250 
251             b2.op(conditionalBranch(p, b3.successor(), b7.successor()));
252 
253             b3.op(conditionalBranch(p, b4.successor(), b5.successor()));
254 
255             b4.op(branch(b6.successor()));
256 
257             b5.op(branch(b6.successor()));
258 
259             b6.op(branch(b8.successor()));
260 
261             b7.op(branch(b8.successor()));
262 
263             b8.op(branch(b9.successor()));
264 
265             b9.op(conditionalBranch(p, b10.successor(), b11.successor()));
266 
267             b10.op(branch(b11.successor()));
268 
269             b11.op(conditionalBranch(p, b12.successor(), b9.successor()));
270 
271             b12.op(conditionalBranch(p, exit.successor(), b2.successor()));
272 
273             exit.op(CoreOp.return_());
274         });
275 
276         System.out.println(f.toText());
277 
278         Map<Block, Block> idoms = f.body().immediateDominators();
279         Node<String> domTree = buildDomTree(f.body().entryBlock(), idoms).transform(b -> Integer.toString(b.index()));
280         Node<String> domTreeExpected =
281                 node("0",
282                         node("1",
283                                 node("2",
284                                         node("7"),
285                                         node("8",
286                                                 node("9",
287                                                         node("10"),
288                                                         node("11",
289                                                                 node("12")))),
290                                         node("3",
291                                                 node("4"), node("5"), node("6")))),
292                         node("13"));
293         Assertions.assertEquals(domTreeExpected, domTree);
294 
295 
296         Map<String, Set<String>> df = f.body().dominanceFrontier().entrySet().stream()
297                 .map(e -> Map.entry(Integer.toString(e.getKey().index()),
298                         e.getValue().stream().map(b -> Integer.toString(b.index())).collect(Collectors.toSet())))
299                 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
300 
301         Map<String, Set<String>> dfExpected = Map.ofEntries(
302                 Map.entry("1", Set.of("13")),
303                 Map.entry("2", Set.of("13", "2")),
304                 Map.entry("3", Set.of("8")),
305                 Map.entry("4", Set.of("6")),
306                 Map.entry("5", Set.of("6")),
307                 Map.entry("6", Set.of("8")),
308                 Map.entry("7", Set.of("8")),
309                 Map.entry("8", Set.of("13", "2")),
310                 Map.entry("9", Set.of("13", "2", "9")),
311                 Map.entry("10", Set.of("11")),
312                 Map.entry("11", Set.of("13", "2", "9")),
313                 Map.entry("12", Set.of("13", "2"))
314         );
315         Assertions.assertEquals(dfExpected, df);
316     }
317 
318     @Test
319     public void testPostDominance() {
320         String m = """
321                 func @"f" (%0 : java.type:"boolean")java.type:"void" -> {
322                     %5 : java.type:"void" = branch ^A;
323 
324                   ^A:
325                     %8 : java.type:"void" = cbranch %0 ^B ^C;
326 
327                   ^B:
328                     %11 : java.type:"void" = branch ^D;
329 
330                   ^C:
331                     %13 : java.type:"void" = branch ^D;
332 
333                   ^D:
334                     %15 : java.type:"void" = branch ^E;
335 
336                   ^E:
337                     %15 : java.type:"void" = branch ^F;
338 
339                   ^F:
340                     %16 : java.type:"void" = cbranch %0 ^E ^END;
341 
342                   ^END:
343                     %18 : java.type:"void" = return;
344                 };
345                 """;
346         CoreOp.FuncOp f = (CoreOp.FuncOp) OpParser.fromStringOfJavaCodeModel(m);
347 
348         Map<Block, Block> ipdoms = f.body().immediatePostDominators();
349         Assertions.assertFalse(ipdoms.containsKey(Body.IPDOM_EXIT));
350 
351         Block exit = ipdoms.containsKey(Body.IPDOM_EXIT) ? Body.IPDOM_EXIT : f.body().blocks().getLast();
352         Node<String> domTree = buildDomTree(exit, ipdoms).transform(b -> Integer.toString(b.index()));
353         Node<String> domTreeExpected =
354                 node("7",
355                         node("6",
356                                 node("5",
357                                         node("4",
358                                                 node("2"),
359                                                 node("3"),
360                                                 node("1",
361                                                         node("0"))))));
362         Assertions.assertEquals(domTreeExpected, domTree);
363     }
364 
365     @Test
366     public void testPostDominanceFrontier() {
367         String m = """
368                 func @"f" (%0 : java.type:"boolean")java.type:"void" -> {
369                     %5 : java.type:"void" = cbranch %0 ^B ^F;
370 
371                   ^B:
372                     %8 : java.type:"void" = cbranch %0 ^C ^D;
373 
374                   ^C:
375                     %11 : java.type:"void" = branch ^E;
376 
377                   ^D:
378                     %13 : java.type:"void" = branch ^E;
379 
380                   ^E:
381                     %15 : java.type:"void" = branch ^F;
382 
383                   ^F:
384                     %18 : java.type:"void" = return;
385                 };
386                 """;
387         CoreOp.FuncOp f = (CoreOp.FuncOp) OpParser.fromStringOfJavaCodeModel(m);
388 
389         Map<Block, Block> ipdoms = f.body().immediatePostDominators();
390         Assertions.assertFalse(ipdoms.containsKey(Body.IPDOM_EXIT));
391 
392         Block exit = ipdoms.containsKey(Body.IPDOM_EXIT) ? Body.IPDOM_EXIT : f.body().blocks().getLast();
393         Node<String> domTree = buildDomTree(exit, ipdoms).transform(b -> Integer.toString(b.index()));
394         Node<String> domTreeExpected =
395                 node("5",
396                         node("4",
397                                 node("1"),
398                                 node("2"),
399                                 node("3")),
400                         node("0"));
401         Assertions.assertEquals(domTreeExpected, domTree);
402 
403         Map<String, Set<String>> df = f.body().postDominanceFrontier().entrySet().stream()
404                 .map(e -> Map.entry(Integer.toString(e.getKey().index()),
405                         e.getValue().stream().map(b -> Integer.toString(b.index())).collect(Collectors.toSet())))
406                 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
407 
408         Map<String, Set<String>> dfExpected = Map.ofEntries(
409                 Map.entry("1", Set.of("0")),
410                 Map.entry("2", Set.of("1")),
411                 Map.entry("3", Set.of("1")),
412                 Map.entry("4", Set.of("0"))
413         );
414         Assertions.assertEquals(dfExpected, df);
415     }
416 
417 
418     static Node<Block> buildDomTree(Block entryBlock, Map<Block, Block> idoms) {
419         Map<Block, Node<Block>> m = new HashMap<>();
420         for (Map.Entry<Block, Block> e : idoms.entrySet()) {
421             Block id = e.getValue();
422             Block b = e.getKey();
423             if (b == entryBlock) {
424                 continue;
425             }
426 
427             Node<Block> parent = m.computeIfAbsent(id, _k -> new Node<>(_k, new HashSet<>()));
428             Node<Block> child = m.computeIfAbsent(b, _k -> new Node<>(_k, new HashSet<>()));
429             parent.children.add(child);
430         }
431         return m.get(entryBlock);
432     }
433 
434     @SafeVarargs
435     static <T> Node<T> node(T t, Node<T>... children) {
436         return new Node<>(t, Set.of(children));
437     }
438 
439     static <T> Node<T> node(T t, Set<Node<T>> children) {
440         return new Node<>(t, children);
441     }
442 
443     record Node<T>(T t, Set<Node<T>> children) {
444         <U> Node<U> transform(Function<T, U> f) {
445             Set<Node<U>> mchildren = new HashSet<>();
446             for (Node<T> nc : children) {
447                 mchildren.add(nc.transform(f));
448             }
449             return node(f.apply(t), mchildren);
450         }
451     }
452 }