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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.incubator.code; 27 28 import jdk.incubator.code.op.CoreOp; 29 import java.util.*; 30 import java.util.function.BiConsumer; 31 import java.util.function.Consumer; 32 import java.util.function.Function; 33 import java.util.stream.Collectors; 34 35 import static jdk.incubator.code.op.CoreOp._return; 36 import static jdk.incubator.code.op.CoreOp.branch; 37 38 /** 39 * A (basic) block containing an ordered sequence of operations, where the last operation is 40 * a {@link Op.Terminating terminating} operation. 41 * <p> 42 * The terminating operation, according to its specification, may branch to other blocks contained in the 43 * same parent body, by way of its {@link Op#successors() successors}, or exit the parent body and optionally 44 * yield a result. 45 * <p> 46 * Blocks may declare one or more block parameters. 47 */ 48 public final class Block implements CodeElement<Block, Op> { 49 50 /** 51 * A value that is a block parameter 52 */ 53 public static final class Parameter extends Value { 54 Parameter(Block block, TypeElement type) { 55 super(block, type); 56 } 57 58 @Override 59 public String toString() { 60 return "%param@" + Integer.toHexString(hashCode()); 61 } 62 63 @Override 64 public Set<Value> dependsOn() { 65 return Set.of(); 66 } 67 68 /** 69 * Returns the invokable operation associated with this block parameter. 70 * <p> 71 * If this block parameter is declared in an entry block and that 72 * block's ancestor operation (the parent of the entry block's parent body) 73 * is an instance of {@link Op.Invokable}, then that instance is returned, 74 * otherwise {@code null} is returned. 75 * <p> 76 * A non-{@code null} result implies this parameter is an invokable parameter. 77 * 78 * @apiNote 79 * This method may be used to pattern match on the returned result: 80 * {@snippet lang = "java": 81 * if (p.invokableOperation() instanceof CoreOp.FuncOp f) { 82 * assert f.parameters().indexOf(p) == p.index(); // @link substring="parameters()" target="Op.Invokable#parameters()" 83 * } 84 *} 85 * 86 * @return the invokable operation, otherwise {@code null} if the operation 87 * is not an instance of {@link Op.Invokable}. 88 * @see Op.Invokable#parameters() 89 */ 90 public Op.Invokable invokableOperation() { 91 if (declaringBlock().isEntryBlock() && 92 declaringBlock().parentBody().parentOp() instanceof Op.Invokable o) { 93 return o; 94 } else { 95 return null; 96 } 97 } 98 99 /** 100 * {@return the index of this block parameter in the parameters of its declaring block.} 101 * @see Value#declaringBlock() 102 * @see Block#parameters() 103 */ 104 public int index() { 105 return declaringBlock().parameters().indexOf(this); 106 } 107 } 108 109 /** 110 * A block reference that refers to a block with arguments. 111 * <p> 112 * A terminating operation may refer, via a block reference, to one or more blocks as its successors. 113 * When control is passed from a block to a successor block the values of the block reference's arguments are 114 * assigned, in order, to the successor block's parameters. 115 */ 116 public static final class Reference { 117 final Block target; 118 final List<Value> arguments; 119 120 /** 121 * Constructs a block reference for a given target block and arguments. 122 * 123 * @param target the target block. 124 * @param arguments the target block arguments, a copy will be made as needed. 125 */ 126 Reference(Block target, List<? extends Value> arguments) { 127 this.target = target; 128 this.arguments = List.copyOf(arguments); 129 } 130 131 /** 132 * {@return the target block.} 133 * @throws IllegalStateException if the target block is partially built 134 */ 135 public Block targetBlock() { 136 if (!isBound()) { 137 throw new IllegalStateException("Target block is partially built"); 138 } 139 140 return target; 141 } 142 143 /** 144 * {@return the block arguments.} 145 */ 146 public List<Value> arguments() { 147 return arguments; 148 } 149 150 boolean isBound() { 151 return target.isBound(); 152 } 153 } 154 155 final Body parentBody; 156 157 final List<Parameter> parameters; 158 159 final List<Op> ops; 160 161 // @@@ In topological order 162 // @@@ Create lazily 163 // Can the representation be more efficient e.g. an array? 164 final SequencedSet<Block> predecessors; 165 166 // Reverse postorder index 167 // Set when block's body has sorted its blocks and therefore set when built 168 // Block is inoperable when < 0 i.e., when partially built 169 int index = -1; 170 171 Block(Body parentBody) { 172 this(parentBody, List.of()); 173 } 174 175 Block(Body parentBody, List<TypeElement> parameterTypes) { 176 this.parentBody = parentBody; 177 this.parameters = new ArrayList<>(); 178 for (TypeElement param : parameterTypes) { 179 parameters.add(new Parameter(this, param)); 180 } 181 this.ops = new ArrayList<>(); 182 this.predecessors = new LinkedHashSet<>(); 183 } 184 185 186 @Override 187 public String toString() { 188 return "^block_" + index + "@" + Integer.toHexString(hashCode()); 189 } 190 191 /** 192 * Returns this block's parent body. 193 * 194 * @return this block's parent body. 195 */ 196 @Override 197 public Body parent() { 198 return parentBody; 199 } 200 201 /** 202 * Returns this block's parent body. 203 * 204 * @return this block's parent body. 205 */ 206 public Body parentBody() { 207 return parentBody; 208 } 209 210 @Override 211 public List<Op> children() { 212 return ops(); 213 } 214 215 /** 216 * Returns the sequence of operations contained in this block. 217 * 218 * @return returns the sequence operations, as an unmodifiable list. 219 */ 220 public List<Op> ops() { 221 return Collections.unmodifiableList(ops); 222 } 223 224 /** 225 * Returns this block's index within the parent body's blocks. 226 * <p> 227 * The following identity holds true: 228 * {@snippet lang = "java" : 229 * this.parentBody().blocks().indexOf(this) == this.index(); 230 * } 231 * 232 * @apiNote 233 * The block's index may be used to efficiently track blocks using 234 * bits sets or boolean arrays. 235 * 236 * @return the block index. 237 */ 238 public int index() { 239 return index; 240 } 241 242 /** 243 * Returns the block parameters. 244 * 245 * @return the block parameters, as an unmodifiable list. 246 */ 247 public List<Parameter> parameters() { 248 return Collections.unmodifiableList(parameters); 249 } 250 251 /** 252 * Returns the block parameter types. 253 * 254 * @return the block parameter types, as am unmodifiable list. 255 */ 256 public List<TypeElement> parameterTypes() { 257 return parameters.stream().map(Value::type).toList(); 258 } 259 260 /** 261 * Finds the operation in this block that is the ancestor of the given operation. 262 * 263 * @param op the given operation. 264 * @return the operation in this block that is the ancestor of the given operation, 265 * otherwise {@code null} 266 */ 267 public Op findAncestorOpInBlock(Op op) { 268 Objects.requireNonNull(op); 269 270 while (op != null && op.parentBlock() != this) { 271 Body encBody = op.ancestorBody(); 272 if (encBody == null) { 273 return null; 274 } 275 276 op = encBody.parentOp(); 277 } 278 279 return op; 280 } 281 282 /** 283 * Returns the first operation in this block. 284 * 285 * @return the first operation in this block. 286 */ 287 public Op firstOp() { 288 return ops.getFirst(); 289 } 290 291 /** 292 * Returns the last, terminating, operation in this block. 293 * <p> 294 * The terminating operation implements {@link Op.Terminating}. 295 * 296 * @return the last, terminating, operation in this block. 297 */ 298 public Op terminatingOp() { 299 Op lop = ops.getLast(); 300 assert lop instanceof Op.Terminating; 301 return lop; 302 } 303 304 /** 305 * Returns the next operation after the given operation, otherwise {@code null} 306 * if this operation is the last operation. 307 * 308 * @param op the operation 309 * @return the next operation after the given operation. 310 */ 311 public Op nextOp(Op op) { 312 int i = ops.indexOf(op); 313 if (i == -1) { 314 throw new IllegalArgumentException(); 315 } 316 return i < ops().size() - 1 ? ops.get(i + 1) : null; 317 } 318 319 /** 320 * Returns the set of predecessors, the set containing each block in the parent 321 * body that refers to this block as a successor. 322 * 323 * @return the set of predecessors, as an unmodifiable set. 324 * @apiNote A block may refer to itself as a successor and therefore also be its predecessor. 325 */ 326 public SequencedSet<Block> predecessors() { 327 return Collections.unmodifiableSequencedSet(predecessors); 328 } 329 330 /** 331 * Returns the list of predecessor references to this block. 332 * <p> 333 * This method behaves is if it returns the result of the following expression: 334 * {@snippet lang = java: 335 * predecessors.stream().flatMap(p->successors().stream()) 336 * .filter(r->r.targetBlock() == this) 337 * .toList(); 338 *} 339 * 340 * @return the list of predecessor references to this block, as an unmodifiable list. 341 * @apiNote A predecessor block may reference it successor block one or more times. 342 */ 343 public List<Block.Reference> predecessorReferences() { 344 return predecessors.stream().flatMap(p -> successors().stream()) 345 .filter(r -> r.targetBlock() == this) 346 .toList(); 347 } 348 349 /** 350 * Returns the list of successors referring to other blocks. 351 * <p> 352 * The successors are declared by the terminating operation contained in this block. 353 * 354 * @return the list of successors, as an unmodifiable list. 355 * @apiNote given a block, A say, whose successor targets a block, B say, we can 356 * state that B is a successor block of A and A is a predecessor block of B. 357 */ 358 public List<Reference> successors() { 359 Op lopr = ops.get(ops.size() - 1); 360 return lopr.successors(); 361 } 362 363 /** 364 * Returns the set of target blocks referred to by the successors of this block. 365 * <p> 366 * This method behaves is if it returns the result of the following expression: 367 * {@snippet lang = java: 368 * successors().stream() 369 * .map(Block.Reference::targetBlock) 370 * .toList(); 371 *} 372 * 373 * @return the list of target blocks, as an unmodifiable set. 374 */ 375 public SequencedSet<Block> successorTargets() { 376 return successors().stream().map(Block.Reference::targetBlock).collect(Collectors.toCollection(LinkedHashSet::new)); 377 } 378 379 /** 380 * Returns true if this block is an entry block. 381 * 382 * @return true if this block is an entry block. 383 */ 384 public boolean isEntryBlock() { 385 return parentBody.entryBlock() == this; 386 } 387 388 /** 389 * Returns {@code true} if this block is 390 * <a href="https://en.wikipedia.org/wiki/Dominator_(graph_theory)">dominated by</a> the given block {@code dom}. 391 * This block is dominated by {@code dom}, if every path from the root entry block to this block passes through 392 * {@code dom}. 393 * <p> 394 * If this block, {@code b} say, and {@code dom} are not in the same parent body, 395 * then {@code b} becomes the nearest ancestor block, result of {@code b.parentBody().parentOp().parentBlock()}, 396 * and so on until either: 397 * {@code b} is {@code null}, therefore {@code b} is <b>not</b> dominated by {@code dom} and this method 398 * returns {@code false}; or 399 * {@code b.parentBody() == dom.parentBody()}, therefore this method returns the result 400 * of {@code b.isDominatedBy(dom)}. 401 * <p> 402 * If this method returns {@code true} then {@code dom.isDominatedBy(this)} 403 * will return {@code false}. However, if this method returns {@code false} then it 404 * does not imply {@code dom.isDominatedBy(this)} returns {@code true}, as neither 405 * block may dominate the other. 406 * 407 * @param dom the dominating block 408 * @return {@code true} if this block is dominated by the given block. 409 */ 410 // @@@ Should this be reversed and named dominates(Block b) 411 public boolean isDominatedBy(Block dom) { 412 Block b = findBlockForDomBody(this, dom.parentBody()); 413 if (b == null) { 414 return false; 415 } 416 417 // A block non-strictly dominates itself 418 if (b == dom) { 419 return true; 420 } 421 422 // The entry block in b's body dominates all other blocks in the body 423 Block entry = b.parentBody().entryBlock(); 424 if (dom == entry) { 425 return true; 426 } 427 428 // Traverse the immediate dominators until dom is reached or the entry block 429 Map<Block, Block> idoms = b.parentBody().immediateDominators(); 430 Block idom = idoms.get(b); 431 while (idom != entry) { 432 if (idom == dom) { 433 return true; 434 } 435 436 idom = idoms.get(idom); 437 } 438 439 return false; 440 } 441 442 /** 443 * Returns the immediate dominator of this block, otherwise {@code null} if this block is the entry block. 444 * Both this block and the immediate dominator (if defined) have the same parent body. 445 * <p> 446 * The immediate dominator is the unique block that strictly dominates this block, but does not strictly dominate 447 * any other block that strictly dominates this block. 448 * 449 * @return the immediate dominator of this block, otherwise {@code null} if this block is the entry block. 450 */ 451 public Block immediateDominator() { 452 if (this == parentBody().entryBlock()) { 453 return null; 454 } 455 456 Map<Block, Block> idoms = parentBody().immediateDominators(); 457 return idoms.get(this); 458 } 459 460 /** 461 * Returns the immediate post dominator of this block, otherwise {@link Body#IPDOM_EXIT} if this block is the 462 * only block with no successors or if this block is one of many blocks that has no successors. 463 * Both this block and the immediate post dominator (if defined) have the same parent body. 464 * <p> 465 * The post immediate dominator is the unique block that strictly post dominates this block, but does not strictly 466 * post dominate any other block that strictly post dominates this block. 467 * 468 * @return the immediate dominator of this block, otherwise {@code null} if this block is the entry block. 469 */ 470 public Block immediatePostDominator() { 471 if (this == parentBody().entryBlock()) { 472 return null; 473 } 474 475 Map<Block, Block> ipdoms = parentBody().immediatePostDominators(); 476 Block ipdom = ipdoms.get(this); 477 return ipdom == this ? Body.IPDOM_EXIT : ipdom; 478 } 479 480 // @@@ isPostDominatedBy and immediatePostDominator 481 482 private static Block findBlockForDomBody(Block b, final Body domr) { 483 Body rb = b.parentBody(); 484 while (domr != rb) { 485 // @@@ What if body is isolated 486 487 b = rb.parentOp().parentBlock(); 488 // null when op is top-level (and its body is isolated), or not yet assigned to block 489 if (b == null) { 490 return null; 491 } 492 rb = b.parentBody(); 493 } 494 return b; 495 } 496 497 /** 498 * A builder of a block. 499 * <p> 500 * When the parent body builder is built this block builder is also built. If a built builder 501 * is operated on to append a block parameter, append an operation, or add a block, then 502 * an {@code IllegalStateException} is thrown. 503 */ 504 public final class Builder implements Function<Op, Op.Result> { 505 final Body.Builder parentBody; 506 final CopyContext cc; 507 final OpTransformer ot; 508 509 Builder(Body.Builder parentBody, CopyContext cc, OpTransformer ot) { 510 this.parentBody = parentBody; 511 this.cc = cc; 512 this.ot = ot; 513 } 514 515 void check() { 516 parentBody.check(); 517 } 518 519 Block target() { 520 return Block.this; 521 } 522 523 /** 524 * {@return the block builder's operation transformer} 525 */ 526 public OpTransformer transformer() { 527 return ot; 528 } 529 530 /** 531 * {@return the block builder's context} 532 */ 533 public CopyContext context() { 534 return cc; 535 } 536 537 /** 538 * {@return the parent body builder} 539 */ 540 public Body.Builder parentBody() { 541 return parentBody; 542 } 543 544 /** 545 * Returns the entry block builder for parent body. 546 * 547 * <p>The returned block is rebound if necessary to this block builder's 548 * context and transformer. 549 * 550 * @return the entry block builder for parent body builder 551 */ 552 public Block.Builder entryBlock() { 553 return parentBody.entryBlock.rebind(cc, ot); 554 } 555 556 /** 557 * {@return true if this block builder is a builder of the entry block} 558 */ 559 public boolean isEntryBlock() { 560 return Block.this == parentBody.target().entryBlock(); 561 } 562 563 /** 564 * Rebinds this block builder with the given context and operation transformer. 565 * 566 * <p>Either this block builder and the returned block builder may be operated on to build 567 * the same block. 568 * Both are equal to each other, and both are closed when the parent body builder is closed. 569 * 570 * @param cc the context 571 * @param ot the operation transformer 572 * @return the rebound block builder 573 */ 574 public Block.Builder rebind(CopyContext cc, OpTransformer ot) { 575 return this.cc == cc && this.ot == ot 576 ? this 577 : this.target().new Builder(parentBody(), cc, ot); 578 } 579 580 /** 581 * Adds a new block to the parent body. 582 * 583 * @param params the block's parameter types 584 * @return the new block builder 585 */ 586 public Block.Builder block(TypeElement... params) { 587 return block(List.of(params)); 588 } 589 590 /** 591 * Adds a new block to the parent body. 592 * 593 * @param params the block's parameter types 594 * @return the new block builder 595 */ 596 public Block.Builder block(List<TypeElement> params) { 597 return parentBody.block(params, cc, ot); 598 } 599 600 /** 601 * Returns an unmodifiable list of the block's parameters. 602 * 603 * @return the unmodifiable list of the block's parameters 604 */ 605 public List<Parameter> parameters() { 606 return Collections.unmodifiableList(parameters); 607 } 608 609 /** 610 * Appends a block parameter to the block's parameters. 611 * 612 * @param p the parameter type 613 * @return the appended block parameter 614 */ 615 public Parameter parameter(TypeElement p) { 616 check(); 617 return appendBlockParameter(p); 618 } 619 620 /** 621 * Creates a reference to this block that can be used as a successor of a terminating operation. 622 * 623 * @param args the block arguments 624 * @return the reference to this block 625 * @throws IllegalStateException if this block builder is associated with the entry block. 626 */ 627 public Reference successor(Value... args) { 628 return successor(List.of(args)); 629 } 630 631 /** 632 * Creates a reference to this block that can be used as a successor of a terminating operation. 633 * 634 * @param args the block arguments 635 * @return the reference to this block 636 * @throws IllegalStateException if this block builder is associated with the entry block. 637 */ 638 public Reference successor(List<? extends Value> args) { 639 if (isEntryBlock()) { 640 throw new IllegalStateException("Entry block cannot be referred to as a successor"); 641 } 642 643 return new Reference(Block.this, List.copyOf(args)); 644 } 645 646 /** 647 * An inline consumer that inserts a return operation with a value, if non-null. 648 */ 649 public static final BiConsumer<Block.Builder, Value> INLINE_RETURN = (block, value) -> { 650 block.op(value != null ? _return(value) : _return()); 651 }; 652 653 /** 654 * Inlines the invokable operation into this block and returns the block builder from which to 655 * continue building. 656 * <p> 657 * This method {@link #transformBody(Body, List, CopyContext, OpTransformer) transforms} the body of the 658 * invokable operation with the given arguments, a new context, and an operation transformer that 659 * replaces return operations by applying the given consumer to a block builder and a return value. 660 * <p> 661 * The operation transformer copies all operations except return operations whose nearest invokable operation 662 * ancestor is the given the invokable operation. When such a return operation is encountered, then on 663 * first encounter of its grandparent body a return block builder is computed and used for this return operation 664 * and encounters of subsequent return operations with the same grandparent body. 665 * <p> 666 * If the grandparent body has only one block then operation transformer's block builder is the return 667 * block builder. Otherwise, if the grandparent body has one or more blocks then the return block builder is 668 * created from the operation transformer's block builder. The created return block builder will have a block 669 * parameter whose type corresponds to the return type, or will have no parameter for void return. 670 * The computation finishes by applying the return block builder and a return value to the inlining consumer. 671 * If the grandparent body has only one block then the return value is the value mapped from the return 672 * operation's operand, or is null for void return. Otherwise, if the grandparent body has one or more blocks 673 * then the value is the block parameter of the created return block builder, or is null for void return. 674 * <p> 675 * For every encounter of a return operation the associated return block builder is compared against the 676 * operation transformer's block builder. If they are not equal then a branch operation is added to the 677 * operation transformer's block builder whose successor is the return block builder with a block argument 678 * that is the value mapped from the return operation's operand, or with no block argument for void return. 679 * @apiNote 680 * It is easier to inline an invokable op if its body is in lowered form (there are no operations in the blocks 681 * of the body that are lowerable). This ensures a single exit point can be created (paired with the single 682 * entry point). If there are one or more nested return operations, then there is unlikely to be a single exit. 683 * Transforming the model to create a single exit point while preserving nested structure is in general 684 * non-trivial and outside the scope of this method. In such cases the invokable operation can be transformed 685 * with a lowering transformation after which it can then be inlined. 686 * 687 * @param invokableOp the invokable operation 688 * @param args the arguments to map to the invokable operation's parameters 689 * @param inlineConsumer the consumer applied to process the return from the invokable operation. 690 * This is called once for each grandparent body of a return operation, with a block to 691 * build replacement operations and the return value, or null for void return. 692 * @return the block builder to continue building from 693 * @param <O> The invokable type 694 */ 695 public <O extends Op & Op.Invokable> Block.Builder inline(O invokableOp, List<? extends Value> args, 696 BiConsumer<Block.Builder, Value> inlineConsumer) { 697 Map<Body, Block.Builder> returnBlocks = new HashMap<>(); 698 // Create new context, ensuring inlining is isolated 699 transformBody(invokableOp.body(), args, CopyContext.create(), (block, op) -> { 700 // If the return operation is associated with the invokable operation 701 if (op instanceof CoreOp.ReturnOp rop && getNearestInvokeableAncestorOp(op) == invokableOp) { 702 // Compute the return block 703 Block.Builder returnBlock = returnBlocks.computeIfAbsent(rop.ancestorBody(), _body -> { 704 Block.Builder rb; 705 // If the body has one block we know there is just one return op declared, otherwise there may 706 // one or more. If so, create a new block that joins all the returns. 707 // Note: we could count all return op in a body to avoid creating a new block for a body 708 // with two or more blocks with only one returnOp is declared. 709 Value r; 710 if (rop.ancestorBody().blocks().size() != 1) { 711 List<TypeElement> param = rop.returnValue() != null 712 ? List.of(invokableOp.invokableType().returnType()) 713 : List.of(); 714 rb = block.block(param); 715 r = !param.isEmpty() 716 ? rb.parameters().get(0) 717 : null; 718 } else { 719 r = rop.returnValue() != null 720 ? block.context().getValue(rop.returnValue()) 721 : null; 722 rb = block; 723 } 724 725 // Inline the return 726 inlineConsumer.accept(rb, r); 727 728 return rb; 729 }); 730 731 // Replace the return op with a branch to the return block, if needed 732 if (!returnBlock.equals(block)) { 733 // Replace return op with branch to return block, with given return value 734 List<Value> arg = rop.returnValue() != null 735 ? List.of(block.context().getValue(rop.returnValue())) 736 : List.of(); 737 block.apply(branch(returnBlock.successor(arg))); 738 } 739 740 return block; 741 } 742 743 block.apply(op); 744 return block; 745 }); 746 747 748 Builder builder = returnBlocks.get(invokableOp.body()); 749 return builder != null ? builder : this; 750 } 751 752 private static Op getNearestInvokeableAncestorOp(Op op) { 753 do { 754 op = op.ancestorBody().parentOp(); 755 } while (!(op instanceof Op.Invokable)); 756 return op; 757 } 758 759 /** 760 * Transforms a body into this block, with this block builder's context. 761 * 762 * @param bodyToTransform the body to transform 763 * @param args the list of output values to map to the input parameters of the body's entry block 764 * @param ot the operation transformer 765 * @see #transformBody(Body, List, CopyContext, OpTransformer) 766 */ 767 public void transformBody(Body bodyToTransform, List<? extends Value> args, 768 OpTransformer ot) { 769 transformBody(bodyToTransform, args, cc, ot); 770 } 771 772 /** 773 * Transforms a body into this block. 774 * <p> 775 * First, a new context is created from the given context and that new context is used to map values and 776 * blocks. 777 * <p> 778 * Second, the entry block is mapped to this block builder rebound with the given operation transformer and 779 * copy context, the input block parameters of the body's entry block are mapped to the given arguments. 780 * <p> 781 * Third, for each input block in the body (except the entry block) an output block builder is created with 782 * equivalent parameters as the input block and with the given operation transformer and copy context. 783 * The input block parameters are mapped to the output block parameters, and the input block is mapped to the 784 * output block builder. 785 * <p> 786 * Fourth, for each input block in the body (in order) the input block's operations are transformed 787 * by applying the output block builder and input block to the given operation transformer. 788 * <p> 789 * When the parent body is built any empty non-entry blocks that have no successors will be removed. 790 * 791 * @param bodyToTransform the body to transform 792 * @param args the list of output values to map to the input parameters of the body's entry block 793 * @param cc the copy context, for values captured in the body 794 * @param ot the operation transformer 795 */ 796 public void transformBody(Body bodyToTransform, List<? extends Value> args, 797 CopyContext cc, OpTransformer ot) { 798 check(); 799 800 // @@@ This might be a new context e.g., when transforming a body 801 cc = CopyContext.create(cc); 802 803 Block entryBlockToTransform = bodyToTransform.entryBlock(); 804 List<Block> blocksToTransform = bodyToTransform.blocks(); 805 806 // Map entry block 807 // Rebind this block builder to the created context and transformer 808 Block.Builder startingBlock = rebind(cc, ot); 809 cc.mapBlock(entryBlockToTransform, startingBlock); 810 cc.mapValues(entryBlockToTransform.parameters(), args); 811 812 // Map subsequent blocks up front, for forward referencing successors 813 for (int i = 1; i < blocksToTransform.size(); i++) { 814 Block blockToTransform = blocksToTransform.get(i); 815 if (cc.getBlock(blockToTransform) != null) { 816 throw new IllegalStateException("Block is already transformed"); 817 } 818 819 // Create block and map block 820 Block.Builder transformedBlock = startingBlock.block(List.of()); 821 for (Block.Parameter ba : blockToTransform.parameters()) { 822 transformedBlock.parameter(ba.type()); 823 } 824 cc.mapBlock(blockToTransform, transformedBlock); 825 cc.mapValues(blockToTransform.parameters(), transformedBlock.parameters()); 826 } 827 828 for (Block blockToTransform : blocksToTransform) { 829 ot.apply(cc.getBlock(blockToTransform), blockToTransform); 830 } 831 } 832 833 /** 834 * Appends operations into the block builder in the scope of the builder as an argument 835 * to the given consumer. 836 * 837 * @param c the consumer. 838 */ 839 // @@@ Is this needed? 840 public void ops(Consumer<Builder> c) { 841 c.accept(this); 842 } 843 844 /** 845 * Appends an operation to this block, with no operation result name, and this builder's transformer. 846 * 847 * @param op the operation to append 848 * @return the operation result of the appended operation 849 * @throws IllegalStateException if the operation is structurally invalid 850 * @see #op(Op, OpTransformer) 851 */ 852 @Override 853 public Op.Result apply(Op op) { 854 return op(op, ot); 855 } 856 857 /** 858 * Appends an operation to this block, with no operation result name, and this builder's transformer. 859 * 860 * @param op the operation to append 861 * @return the operation result of the appended operation 862 * @throws IllegalStateException if the operation is structurally invalid 863 * @see #op(Op, OpTransformer) 864 */ 865 public Op.Result op(Op op) { 866 return op(op, ot); 867 } 868 869 /** 870 * Appends an operation to this block. 871 * <p> 872 * If the operation is not bound to a block, then the operation is appended and bound to this block. 873 * Otherwise, if the operation is bound, the operation is first 874 * {@link Op#transform(CopyContext, OpTransformer) transformed} with this builder's context and the given 875 * operation transformer, the unbound transformed operation is appended, and the operation's result is mapped 876 * to the transformed operation's result (using the builder's context). 877 * <p> 878 * If the unbound operation (transformed, or otherwise) is structurally invalid then an 879 * {@code IllegalStateException} is thrown. An unbound operation is structurally invalid if: 880 * <ul> 881 * <li>any of its bodies does not have the same ancestor body as this block's parent body. 882 * <li>any of its operands (values) is not reachable from this block. 883 * <li>any of its successors is not a sibling of this block. 884 * <li>any of its successors arguments (values) is not reachable from this block. 885 * </ul> 886 * A value is reachable from this block if there is a path from this block's parent body, 887 * via its ancestor bodies, to the value's block's parent body. (Note this structural check 888 * ensures values are only used from the same tree being built, but it is weaker than a 889 * dominance check that may be performed when the parent body is built.) 890 * 891 * @param op the operation to append 892 * @param transformer the transformer to use when appending a bound operation 893 * @return the operation result of the appended operation 894 * @throws IllegalStateException if the operation is structurally invalid 895 */ 896 public Op.Result op(Op op, OpTransformer transformer) { 897 check(); 898 final Op.Result oprToTransform = op.result(); 899 900 Op transformedOp = op; 901 if (oprToTransform != null) { 902 // If operation is assigned to block, then copy it and transform its contents 903 transformedOp = op.transform(cc, transformer); 904 assert transformedOp.result == null; 905 } 906 907 Op.Result transformedOpr = insertOp(transformedOp); 908 909 if (oprToTransform != null) { 910 // Map the result of the first transformation 911 // @@@ If the same operation is transformed more than once then subsequent 912 // transformed ops will not get implicitly mapped 913 // Should this be an error? 914 cc.mapValueIfAbsent(oprToTransform, transformedOpr); 915 } 916 917 return transformedOpr; 918 } 919 920 /** 921 * Returns true if this block builder is equal to the other object. 922 * <p>This block builder is equal if the other object is an instance of a block builder, and they are 923 * associated with the same block (but perhaps bound to different contexts and transformers). 924 * 925 * @param o the other object 926 * @return true if this builder is equal to the other object. 927 */ 928 @Override 929 public boolean equals(Object o) { 930 if (this == o) return true; 931 return o instanceof Builder that && Block.this == that.target(); 932 } 933 934 @Override 935 public int hashCode() { 936 return Block.this.hashCode(); 937 } 938 } 939 940 // Modifying methods 941 942 // Create block parameter associated with this block 943 private Parameter appendBlockParameter(TypeElement type) { 944 Parameter blockParameter = new Parameter(this, type); 945 parameters.add(blockParameter); 946 947 return blockParameter; 948 } 949 950 // Create an operation, adding to the end of the list of existing operations 951 private Op.Result insertOp(Op op) { 952 Op.Result opResult = new Op.Result(this, op); 953 bindOp(opResult, op); 954 955 ops.add(op); 956 return opResult; 957 } 958 959 private void bindOp(Op.Result opr, Op op) { 960 // Structural checks 961 if (!ops.isEmpty() && ops.getLast() instanceof Op.Terminating) { 962 throw new IllegalStateException("Operation cannot be appended, the block has a terminal operation"); 963 } 964 965 for (Body b : op.bodies()) { 966 if (b.ancestorBody != null && b.ancestorBody != this.parentBody) { 967 throw new IllegalStateException("Body of operation is bound to a different ancestor body: "); 968 } 969 } 970 971 for (Value v : op.operands()) { 972 if (!isReachable(v)) { 973 throw new IllegalStateException( 974 String.format("Operand of operation %s is not defined in tree: %s", op, v)); 975 } 976 assert !v.isBound(); 977 } 978 979 for (Reference s : op.successors()) { 980 if (s.target.parentBody != this.parentBody) { 981 throw new IllegalStateException("Target of block reference is not a sibling of this block"); 982 } 983 984 for (Value v : s.arguments()) { 985 if (!isReachable(v)) { 986 throw new IllegalStateException( 987 String.format("Argument of block reference %s of terminating operation %s is not defined in tree: %s", s, op, v)); 988 } 989 assert !v.isBound(); 990 } 991 } 992 993 // State updates after structural checks 994 // @@@ The alternative is to close the body builder on failure, rendering it inoperable, 995 // so checks and updates can be merged 996 for (Value v : op.operands()) { 997 v.uses.add(opr); 998 } 999 1000 for (Reference s : op.successors()) { 1001 for (Value v : s.arguments()) { 1002 v.uses.add(opr); 1003 } 1004 1005 s.target.predecessors.add(Block.this); 1006 } 1007 1008 op.result = opr; 1009 } 1010 1011 // Determine if the parent body of value's block is an ancestor of this block 1012 private boolean isReachable(Value v) { 1013 Body b = parentBody; 1014 while (b != null && b != v.block.parentBody) { 1015 b = b.ancestorBody; 1016 } 1017 return b != null; 1018 } 1019 1020 // 1021 1022 boolean isBound() { 1023 return index >= 0; 1024 } 1025 }