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