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