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 java.lang.reflect.code.op;
  27 
  28 import java.lang.constant.ClassDesc;
  29 import java.lang.reflect.code.*;
  30 import java.lang.reflect.code.type.ArrayType;
  31 import java.lang.reflect.code.type.ClassType;
  32 import java.lang.reflect.code.type.MethodRef;
  33 import java.lang.reflect.code.type.RecordTypeRef;
  34 import java.lang.reflect.code.type.FunctionType;
  35 import java.lang.reflect.code.type.JavaType;
  36 import java.lang.reflect.code.type.TupleType;
  37 import java.lang.reflect.code.TypeElement;
  38 import java.lang.reflect.code.type.VarType;
  39 import java.util.*;
  40 import java.util.concurrent.atomic.AtomicBoolean;
  41 import java.util.function.Consumer;
  42 import java.util.function.Function;
  43 import java.util.stream.Stream;
  44 
  45 import static java.lang.reflect.code.op.CoreOp.*;
  46 import static java.lang.reflect.code.type.JavaType.*;
  47 
  48 /**
  49  * The top-level operation class for the enclosed set of extended operations.
  50  * <p>
  51  * A code model, produced by the Java compiler from Java program source, may consist of extended operations and core
  52  * operations. Such a model represents the same Java program and preserves the program meaning as defined by the
  53  * Java Language Specification
  54  * <p>
  55  * Extended operations model specific Java language constructs, often those with structured control flow and nested
  56  * code. Each operation is transformable into a sequence of core operations, commonly referred to as lowering. Those
  57  * that implement {@link Op.Lowerable} can transform themselves and will transform associated extended operations
  58  * that are not explicitly lowerable.
  59  * <p>
  60  * A code model, produced by the Java compiler from source, and consisting of extended operations and core operations
  61  * can be transformed to one consisting only of core operations, where all extended operations are lowered. This
  62  * transformation preserves programing meaning. The resulting lowered code model also represents the same Java program.
  63  */
  64 public sealed abstract class ExtendedOp extends ExternalizableOp {
  65     // Split string to ensure the name does not get rewritten
  66     // when the script copies this source to the jdk.compiler module
  67     static final String PACKAGE_NAME = "java.lang" + ".reflect.code";
  68 
  69     static final String ExtendedOp_CLASS_NAME = PACKAGE_NAME + "." + ExtendedOp.class.getSimpleName();
  70 
  71     protected ExtendedOp(Op that, CopyContext cc) {
  72         super(that, cc);
  73     }
  74 
  75     protected ExtendedOp(String name, List<? extends Value> operands) {
  76         super(name, operands);
  77     }
  78 
  79     protected ExtendedOp(ExternalizableOp.ExternalizedOp def) {
  80         super(def);
  81     }
  82 
  83 
  84     /**
  85      * The label operation, that can model Java language statements with label identifiers.
  86      */
  87     public sealed static abstract class JavaLabelOp extends ExtendedOp
  88             implements Op.Lowerable, Op.BodyTerminating, JavaStatement {
  89         JavaLabelOp(ExternalizedOp def) {
  90             super(def);
  91 
  92             if (def.operands().size() > 1) {
  93                 throw new IllegalArgumentException("Operation must have zero or one operand " + def.name());
  94             }
  95         }
  96 
  97         JavaLabelOp(JavaLabelOp that, CopyContext cc) {
  98             super(that, cc);
  99         }
 100 
 101         JavaLabelOp(String name, Value label) {
 102             super(name, checkLabel(label));
 103         }
 104 
 105         static List<Value> checkLabel(Value label) {
 106             return label == null ? List.of() : List.of(label);
 107         }
 108 
 109         Op innerMostEnclosingTarget() {
 110             /*
 111                 A break statement with no label attempts to transfer control to the
 112                 innermost enclosing switch, while, do, or for statement; this enclosing statement,
 113                 which is called the break target, then immediately completes normally.
 114 
 115                 A break statement with label Identifier attempts to transfer control to the
 116                 enclosing labeled statement (14.7) that has the same Identifier as its label;
 117                 this enclosing statement, which is called the break target, then immediately completes normally.
 118                 In this case, the break target need not be a switch, while, do, or for statement.
 119              */
 120 
 121             // No label
 122             // Get innermost enclosing loop operation
 123             // @@@ expand to support innermost enclosing switch operation
 124             Op op = this;
 125             Body b;
 126             do {
 127                 b = op.ancestorBody();
 128                 op = b.parentOp();
 129                 if (op == null) {
 130                     throw new IllegalStateException("No enclosing loop");
 131                 }
 132             } while (!(op instanceof Op.Loop));
 133             // } while (!(op instanceof Op.Loop lop));
 134             // error: variable lop might not have been initialized
 135             Op.Loop lop = (Op.Loop) op;
 136             return lop.loopBody() == b ? op : null;
 137         }
 138 
 139         boolean isUnlabeled() {
 140             return operands().isEmpty();
 141         }
 142 
 143         Op target() {
 144             // If unlabeled then find the nearest enclosing op
 145             // Otherwise obtain the label target
 146             if (isUnlabeled()) {
 147                 return innerMostEnclosingTarget();
 148             }
 149 
 150             Value value = operands().get(0);
 151             if (value instanceof Result r && r.op().ancestorBody().parentOp() instanceof JavaLabeledOp lop) {
 152                 return lop.target();
 153             } else {
 154                 throw new IllegalStateException("Bad label value: " + value + " " + ((Result) value).op());
 155             }
 156         }
 157 
 158         Block.Builder lower(Block.Builder b, Function<BranchTarget, Block.Builder> f) {
 159             Op opt = target();
 160             BranchTarget t = getBranchTarget(b.context(), opt);
 161             if (t != null) {
 162                 b.op(branch(f.apply(t).successor()));
 163             } else {
 164                 throw new IllegalStateException("No branch target for operation: " + opt);
 165             }
 166             return b;
 167         }
 168 
 169         @Override
 170         public TypeElement resultType() {
 171             return VOID;
 172         }
 173     }
 174 
 175     /**
 176      * The break operation, that can model Java language break statements with label identifiers.
 177      */
 178     @OpFactory.OpDeclaration(JavaBreakOp.NAME)
 179     public static final class JavaBreakOp extends JavaLabelOp {
 180         public static final String NAME = "java.break";
 181 
 182         public JavaBreakOp(ExternalizedOp def) {
 183             super(def);
 184         }
 185 
 186         JavaBreakOp(JavaBreakOp that, CopyContext cc) {
 187             super(that, cc);
 188         }
 189 
 190         @Override
 191         public JavaBreakOp transform(CopyContext cc, OpTransformer ot) {
 192             return new JavaBreakOp(this, cc);
 193         }
 194 
 195         JavaBreakOp(Value label) {
 196             super(NAME, label);
 197         }
 198 
 199         @Override
 200         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
 201             return lower(b, BranchTarget::breakBlock);
 202         }
 203     }
 204 
 205     /**
 206      * The break operation, that can model Java language continue statements with label identifiers.
 207      */
 208     @OpFactory.OpDeclaration(JavaContinueOp.NAME)
 209     public static final class JavaContinueOp extends JavaLabelOp {
 210         public static final String NAME = "java.continue";
 211 
 212         public JavaContinueOp(ExternalizedOp def) {
 213             super(def);
 214         }
 215 
 216         JavaContinueOp(JavaContinueOp that, CopyContext cc) {
 217             super(that, cc);
 218         }
 219 
 220         @Override
 221         public JavaContinueOp transform(CopyContext cc, OpTransformer ot) {
 222             return new JavaContinueOp(this, cc);
 223         }
 224 
 225         JavaContinueOp(Value label) {
 226             super(NAME, label);
 227         }
 228 
 229         @Override
 230         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
 231             return lower(b, BranchTarget::continueBlock);
 232         }
 233     }
 234 
 235     record BranchTarget(Block.Builder breakBlock, Block.Builder continueBlock) {
 236     }
 237 
 238     static final String BRANCH_TARGET_MAP_PROPERTY_KEY = "BRANCH_TARGET_MAP";
 239 
 240     static BranchTarget getBranchTarget(CopyContext cc, Op op) {
 241         @SuppressWarnings("unchecked")
 242         Map<Op, BranchTarget> m = (Map<Op, BranchTarget>) cc.getProperty(BRANCH_TARGET_MAP_PROPERTY_KEY);
 243         if (m != null) {
 244             return m.get(op);
 245         }
 246 
 247         return null;
 248     }
 249 
 250     static void setBranchTarget(CopyContext cc, Op label, BranchTarget t) {
 251         @SuppressWarnings("unchecked")
 252         Map<Op, BranchTarget> x = (Map<Op, BranchTarget>) cc.computePropertyIfAbsent(
 253                 BRANCH_TARGET_MAP_PROPERTY_KEY, k -> new HashMap<>());
 254         x.put(label, t);
 255     }
 256 
 257     /**
 258      * The yield operation, that can model Java language yield statements.
 259      */
 260     @OpFactory.OpDeclaration(JavaYieldOp.NAME)
 261     public static final class JavaYieldOp extends ExtendedOp
 262             implements Op.BodyTerminating, JavaStatement {
 263         public static final String NAME = "java.yield";
 264 
 265         public JavaYieldOp(ExternalizedOp def) {
 266             super(def);
 267         }
 268 
 269         JavaYieldOp(JavaYieldOp that, CopyContext cc) {
 270             super(that, cc);
 271         }
 272 
 273         @Override
 274         public JavaYieldOp transform(CopyContext cc, OpTransformer ot) {
 275             return new JavaYieldOp(this, cc);
 276         }
 277 
 278         JavaYieldOp() {
 279             super(NAME,
 280                     List.of());
 281         }
 282 
 283         JavaYieldOp(Value operand) {
 284             super(NAME, List.of(operand));
 285         }
 286 
 287         public Value yieldValue() {
 288             if (operands().size() == 1) {
 289                 return operands().get(0);
 290             } else {
 291                 // @@@
 292                 return null;
 293             }
 294         }
 295 
 296         @Override
 297         public TypeElement resultType() {
 298             return VOID;
 299         }
 300     }
 301 
 302     /**
 303      * The block operation, that can model Java language blocks.
 304      */
 305     @OpFactory.OpDeclaration(JavaBlockOp.NAME)
 306     // @@@ Support synchronized attribute
 307     public static final class JavaBlockOp extends ExtendedOp
 308             implements Op.Nested, Op.Lowerable, JavaStatement {
 309         public static final String NAME = "java.block";
 310 
 311         final Body body;
 312 
 313         public JavaBlockOp(ExternalizedOp def) {
 314             super(def);
 315 
 316             if (!def.operands().isEmpty()) {
 317                 throw new IllegalStateException("Operation must have no operands");
 318             }
 319 
 320             this.body = def.bodyDefinitions().get(0).build(this);
 321         }
 322 
 323         JavaBlockOp(JavaBlockOp that, CopyContext cc, OpTransformer ot) {
 324             super(that, cc);
 325 
 326             // Copy body
 327             this.body = that.body.transform(cc, ot).build(this);
 328         }
 329 
 330         @Override
 331         public JavaBlockOp transform(CopyContext cc, OpTransformer ot) {
 332             return new JavaBlockOp(this, cc, ot);
 333         }
 334 
 335         // @@@ Support non-void result type
 336         JavaBlockOp(Body.Builder bodyC) {
 337             super(NAME, List.of());
 338 
 339             this.body = bodyC.build(this);
 340             if (!body.bodyType().returnType().equals(VOID)) {
 341                 throw new IllegalArgumentException("Body should return void: " + body.bodyType());
 342             }
 343             if (!body.bodyType().parameterTypes().isEmpty()) {
 344                 throw new IllegalArgumentException("Body should have zero parameters: " + body.bodyType());
 345             }
 346         }
 347 
 348         @Override
 349         public List<Body> bodies() {
 350             return List.of(body);
 351         }
 352 
 353         public Body body() {
 354             return body;
 355         }
 356 
 357         @Override
 358         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
 359             Block.Builder exit = b.block();
 360             setBranchTarget(b.context(), this, new BranchTarget(exit, null));
 361 
 362             b.transformBody(body, List.of(), opT.andThen((block, op) -> {
 363                 if (op instanceof YieldOp) {
 364                     block.op(branch(exit.successor()));
 365                 } else {
 366                     // @@@ Composition of lowerable ops
 367                     if (op instanceof Lowerable lop) {
 368                         block = lop.lower(block, opT);
 369                     } else {
 370                         block.op(op);
 371                     }
 372                 }
 373                 return block;
 374             }));
 375 
 376             return exit;
 377         }
 378 
 379         @Override
 380         public TypeElement resultType() {
 381             return VOID;
 382         }
 383     }
 384 
 385     /**
 386      * The labeled operation, that can model Java language labeled statements.
 387      */
 388     @OpFactory.OpDeclaration(JavaLabeledOp.NAME)
 389     public static final class JavaLabeledOp extends ExtendedOp
 390             implements Op.Nested, Op.Lowerable, JavaStatement {
 391         public static final String NAME = "java.labeled";
 392 
 393         final Body body;
 394 
 395         public JavaLabeledOp(ExternalizedOp def) {
 396             super(def);
 397 
 398             if (!def.operands().isEmpty()) {
 399                 throw new IllegalStateException("Operation must have no operands");
 400             }
 401 
 402             this.body = def.bodyDefinitions().get(0).build(this);
 403         }
 404 
 405         JavaLabeledOp(JavaLabeledOp that, CopyContext cc, OpTransformer ot) {
 406             super(that, cc);
 407 
 408             // Copy body
 409             this.body = that.body.transform(cc, ot).build(this);
 410         }
 411 
 412         @Override
 413         public JavaLabeledOp transform(CopyContext cc, OpTransformer ot) {
 414             return new JavaLabeledOp(this, cc, ot);
 415         }
 416 
 417         JavaLabeledOp(Body.Builder bodyC) {
 418             super(NAME, List.of());
 419 
 420             this.body = bodyC.build(this);
 421             if (!body.bodyType().returnType().equals(VOID)) {
 422                 throw new IllegalArgumentException("Body should return void: " + body.bodyType());
 423             }
 424             if (!body.bodyType().parameterTypes().isEmpty()) {
 425                 throw new IllegalArgumentException("Body should have zero parameters: " + body.bodyType());
 426             }
 427         }
 428 
 429         @Override
 430         public List<Body> bodies() {
 431             return List.of(body);
 432         }
 433 
 434         public Op label() {
 435             return body.entryBlock().firstOp();
 436         }
 437 
 438         public Op target() {
 439             return body.entryBlock().nextOp(label());
 440         }
 441 
 442         @Override
 443         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
 444             Block.Builder exit = b.block();
 445             setBranchTarget(b.context(), this, new BranchTarget(exit, null));
 446 
 447             AtomicBoolean first = new AtomicBoolean();
 448             b.transformBody(body, List.of(), opT.andThen((block, op) -> {
 449                 // Drop first operation that corresponds to the label
 450                 if (!first.get()) {
 451                     first.set(true);
 452                     return block;
 453                 }
 454 
 455                 if (op instanceof YieldOp) {
 456                     block.op(branch(exit.successor()));
 457                 } else {
 458                     // @@@ Composition of lowerable ops
 459                     if (op instanceof Lowerable lop) {
 460                         block = lop.lower(block, opT);
 461                     } else {
 462                         block.op(op);
 463                     }
 464                 }
 465                 return block;
 466             }));
 467 
 468             return exit;
 469         }
 470 
 471         @Override
 472         public TypeElement resultType() {
 473             return VOID;
 474         }
 475     }
 476 
 477     /**
 478      * The if operation, that can model Java language if, if-then, and if-then-else statements.
 479      */
 480     @OpFactory.OpDeclaration(JavaIfOp.NAME)
 481     public static final class JavaIfOp extends ExtendedOp
 482             implements Op.Nested, Op.Lowerable, JavaStatement {
 483 
 484         static final FunctionType PREDICATE_TYPE = FunctionType.functionType(BOOLEAN);
 485 
 486         static final FunctionType ACTION_TYPE = FunctionType.VOID;
 487 
 488         public static class IfBuilder {
 489             final Body.Builder ancestorBody;
 490             final List<Body.Builder> bodies;
 491 
 492             IfBuilder(Body.Builder ancestorBody) {
 493                 this.ancestorBody = ancestorBody;
 494                 this.bodies = new ArrayList<>();
 495             }
 496 
 497             public ThenBuilder _if(Consumer<Block.Builder> c) {
 498                 Body.Builder body = Body.Builder.of(ancestorBody, PREDICATE_TYPE);
 499                 c.accept(body.entryBlock());
 500                 bodies.add(body);
 501 
 502                 return new ThenBuilder(ancestorBody, bodies);
 503             }
 504         }
 505 
 506         public static class ThenBuilder {
 507             final Body.Builder ancestorBody;
 508             final List<Body.Builder> bodies;
 509 
 510             public ThenBuilder(Body.Builder ancestorBody, List<Body.Builder> bodies) {
 511                 this.ancestorBody = ancestorBody;
 512                 this.bodies = bodies;
 513             }
 514 
 515             public ElseIfBuilder then(Consumer<Block.Builder> c) {
 516                 Body.Builder body = Body.Builder.of(ancestorBody, ACTION_TYPE);
 517                 c.accept(body.entryBlock());
 518                 bodies.add(body);
 519 
 520                 return new ElseIfBuilder(ancestorBody, bodies);
 521             }
 522 
 523             public ElseIfBuilder then() {
 524                 Body.Builder body = Body.Builder.of(ancestorBody, ACTION_TYPE);
 525                 body.entryBlock().op(_yield());
 526                 bodies.add(body);
 527 
 528                 return new ElseIfBuilder(ancestorBody, bodies);
 529             }
 530         }
 531 
 532         public static class ElseIfBuilder {
 533             final Body.Builder ancestorBody;
 534             final List<Body.Builder> bodies;
 535 
 536             public ElseIfBuilder(Body.Builder ancestorBody, List<Body.Builder> bodies) {
 537                 this.ancestorBody = ancestorBody;
 538                 this.bodies = bodies;
 539             }
 540 
 541             public ThenBuilder elseif(Consumer<Block.Builder> c) {
 542                 Body.Builder body = Body.Builder.of(ancestorBody, PREDICATE_TYPE);
 543                 c.accept(body.entryBlock());
 544                 bodies.add(body);
 545 
 546                 return new ThenBuilder(ancestorBody, bodies);
 547             }
 548 
 549             public JavaIfOp _else(Consumer<Block.Builder> c) {
 550                 Body.Builder body = Body.Builder.of(ancestorBody, ACTION_TYPE);
 551                 c.accept(body.entryBlock());
 552                 bodies.add(body);
 553 
 554                 return new JavaIfOp(bodies);
 555             }
 556 
 557             public JavaIfOp _else() {
 558                 Body.Builder body = Body.Builder.of(ancestorBody, ACTION_TYPE);
 559                 body.entryBlock().op(_yield());
 560                 bodies.add(body);
 561 
 562                 return new JavaIfOp(bodies);
 563             }
 564         }
 565 
 566         public static final String NAME = "java.if";
 567 
 568         final List<Body> bodies;
 569 
 570         public JavaIfOp(ExternalizedOp def) {
 571             super(def);
 572 
 573             if (!def.operands().isEmpty()) {
 574                 throw new IllegalStateException("Operation must have no operands");
 575             }
 576 
 577             // @@@ Validate
 578 
 579             this.bodies = def.bodyDefinitions().stream().map(bd -> bd.build(this)).toList();
 580         }
 581 
 582         JavaIfOp(JavaIfOp that, CopyContext cc, OpTransformer ot) {
 583             super(that, cc);
 584 
 585             // Copy body
 586             this.bodies = that.bodies.stream()
 587                     .map(b -> b.transform(cc, ot).build(this)).toList();
 588         }
 589 
 590         @Override
 591         public JavaIfOp transform(CopyContext cc, OpTransformer ot) {
 592             return new JavaIfOp(this, cc, ot);
 593         }
 594 
 595         JavaIfOp(List<Body.Builder> bodyCs) {
 596             super(NAME, List.of());
 597 
 598             // Normalize by adding an empty else action
 599             // @@@ Is this needed?
 600             if (bodyCs.size() % 2 == 0) {
 601                 bodyCs = new ArrayList<>(bodyCs);
 602                 Body.Builder end = Body.Builder.of(bodyCs.get(0).ancestorBody(),
 603                         FunctionType.VOID);
 604                 end.entryBlock().op(_yield());
 605                 bodyCs.add(end);
 606             }
 607 
 608             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
 609 
 610             if (bodies.size() < 2) {
 611                 throw new IllegalArgumentException("Incorrect number of bodies: " + bodies.size());
 612             }
 613             for (int i = 0; i < bodies.size(); i += 2) {
 614                 Body action;
 615                 if (i == bodies.size() - 1) {
 616                     action = bodies.get(i);
 617                 } else {
 618                     action = bodies.get(i + 1);
 619                     Body fromPred = bodies.get(i);
 620                     if (!fromPred.bodyType().equals(FunctionType.functionType(BOOLEAN))) {
 621                         throw new IllegalArgumentException("Illegal predicate body descriptor: " + fromPred.bodyType());
 622                     }
 623                 }
 624                 if (!action.bodyType().equals(FunctionType.VOID)) {
 625                     throw new IllegalArgumentException("Illegal action body descriptor: " + action.bodyType());
 626                 }
 627             }
 628         }
 629 
 630         @Override
 631         public List<Body> bodies() {
 632             return bodies;
 633         }
 634 
 635         @Override
 636         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
 637             Block.Builder exit = b.block();
 638             setBranchTarget(b.context(), this, new BranchTarget(exit, null));
 639 
 640             // Create predicate and action blocks
 641             List<Block.Builder> builders = new ArrayList<>();
 642             for (int i = 0; i < bodies.size(); i += 2) {
 643                 if (i == bodies.size() - 1) {
 644                     builders.add(b.block());
 645                 } else {
 646                     builders.add(i == 0 ? b : b.block());
 647                     builders.add(b.block());
 648                 }
 649             }
 650 
 651             for (int i = 0; i < bodies.size(); i += 2) {
 652                 Body actionBody;
 653                 Block.Builder action;
 654                 if (i == bodies.size() - 1) {
 655                     actionBody = bodies.get(i);
 656                     action = builders.get(i);
 657                 } else {
 658                     Body predBody = bodies.get(i);
 659                     actionBody = bodies.get(i + 1);
 660 
 661                     Block.Builder pred = builders.get(i);
 662                     action = builders.get(i + 1);
 663                     Block.Builder next = builders.get(i + 2);
 664 
 665                     pred.transformBody(predBody, List.of(), opT.andThen((block, op) -> {
 666                         if (op instanceof YieldOp yo) {
 667                             block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
 668                                     action.successor(), next.successor()));
 669                         } else if (op instanceof Lowerable lop) {
 670                             // @@@ Composition of lowerable ops
 671                             block = lop.lower(block, opT);
 672                         } else {
 673                             block.op(op);
 674                         }
 675                         return block;
 676                     }));
 677                 }
 678 
 679                 action.transformBody(actionBody, List.of(), opT.andThen((block, op) -> {
 680                     if (op instanceof YieldOp) {
 681                         block.op(branch(exit.successor()));
 682                     } else {
 683                         // @@@ Composition of lowerable ops
 684                         if (op instanceof Lowerable lop) {
 685                             block = lop.lower(block, opT);
 686                         } else {
 687                             block.op(op);
 688                         }
 689                     }
 690                     return block;
 691                 }));
 692             }
 693 
 694             return exit;
 695         }
 696 
 697         @Override
 698         public TypeElement resultType() {
 699             return VOID;
 700         }
 701     }
 702 
 703     /**
 704      * The switch expression operation, that can model Java language switch expressions.
 705      */
 706     @OpFactory.OpDeclaration(JavaSwitchExpressionOp.NAME)
 707     public static final class JavaSwitchExpressionOp extends ExtendedOp
 708             implements Op.Nested, Op.Lowerable, JavaExpression {
 709         public static final String NAME = "java.switch.expression";
 710 
 711         final TypeElement resultType;
 712         final List<Body> bodies;
 713 
 714         public JavaSwitchExpressionOp(ExternalizedOp def) {
 715             super(def);
 716 
 717             if (def.operands().size() != 1) {
 718                 throw new IllegalStateException("Operation must have one operand");
 719             }
 720 
 721             // @@@ Validate
 722 
 723             this.bodies = def.bodyDefinitions().stream().map(bd -> bd.build(this)).toList();
 724             this.resultType = def.resultType();
 725         }
 726 
 727         JavaSwitchExpressionOp(JavaSwitchExpressionOp that, CopyContext cc, OpTransformer ot) {
 728             super(that, cc);
 729 
 730             // Copy body
 731             this.bodies = that.bodies.stream()
 732                     .map(b -> b.transform(cc, ot).build(this)).toList();
 733             this.resultType = that.resultType;
 734         }
 735 
 736         @Override
 737         public JavaSwitchExpressionOp transform(CopyContext cc, OpTransformer ot) {
 738             return new JavaSwitchExpressionOp(this, cc, ot);
 739         }
 740 
 741         JavaSwitchExpressionOp(TypeElement resultType, Value target, List<Body.Builder> bodyCs) {
 742             super(NAME, List.of(target));
 743 
 744             // Each case is modelled as a contiguous pair of bodies
 745             // The first body models the case labels, and the second models the case expression or statements
 746             // The labels body has a parameter whose type is target operand's type and returns a boolean value
 747             // The statements/expression body has no parameters and returns the result whose type is the result of
 748             // the switch expression
 749             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
 750             // @@@ when resultType is null, we assume statements/expressions bodies have the same yieldType
 751             this.resultType = resultType == null ? bodies.get(1).yieldType() : resultType;
 752         }
 753 
 754         @Override
 755         public List<Body> bodies() {
 756             return bodies;
 757         }
 758 
 759         @Override
 760         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
 761             throw new UnsupportedOperationException();
 762         }
 763 
 764         @Override
 765         public TypeElement resultType() {
 766             return resultType;
 767         }
 768     }
 769 
 770     /**
 771      * The switch fall-through operation, that can model fall-through to the next statement in the switch block after
 772      * the last statement of the current switch label.
 773      */
 774     @OpFactory.OpDeclaration(JavaSwitchFallthroughOp.NAME)
 775     public static final class JavaSwitchFallthroughOp extends ExtendedOp
 776             implements Op.BodyTerminating {
 777         public static final String NAME = "java.switch.fallthrough";
 778 
 779         public JavaSwitchFallthroughOp(ExternalizedOp def) {
 780             super(def);
 781         }
 782 
 783         JavaSwitchFallthroughOp(JavaSwitchFallthroughOp that, CopyContext cc) {
 784             super(that, cc);
 785         }
 786 
 787         @Override
 788         public JavaSwitchFallthroughOp transform(CopyContext cc, OpTransformer ot) {
 789             return new JavaSwitchFallthroughOp(this, cc);
 790         }
 791 
 792         JavaSwitchFallthroughOp() {
 793             super(NAME, List.of());
 794         }
 795 
 796         @Override
 797         public TypeElement resultType() {
 798             return VOID;
 799         }
 800     }
 801 
 802     /**
 803      * The for operation, that can model a Java language for statement.
 804      */
 805     @OpFactory.OpDeclaration(JavaForOp.NAME)
 806     public static final class JavaForOp extends ExtendedOp
 807             implements Op.Loop, Op.Lowerable, JavaStatement {
 808 
 809         public static final class InitBuilder {
 810             final Body.Builder ancestorBody;
 811             final List<? extends TypeElement> initTypes;
 812 
 813             InitBuilder(Body.Builder ancestorBody,
 814                         List<? extends TypeElement> initTypes) {
 815                 this.ancestorBody = ancestorBody;
 816                 this.initTypes = initTypes.stream().map(VarType::varType).toList();
 817             }
 818 
 819             public JavaForOp.CondBuilder init(Consumer<Block.Builder> c) {
 820                 Body.Builder init = Body.Builder.of(ancestorBody,
 821                         FunctionType.functionType(TupleType.tupleType(initTypes)));
 822                 c.accept(init.entryBlock());
 823 
 824                 return new CondBuilder(ancestorBody, initTypes, init);
 825             }
 826         }
 827 
 828         public static final class CondBuilder {
 829             final Body.Builder ancestorBody;
 830             final List<? extends TypeElement> initTypes;
 831             final Body.Builder init;
 832 
 833             public CondBuilder(Body.Builder ancestorBody,
 834                                List<? extends TypeElement> initTypes,
 835                                Body.Builder init) {
 836                 this.ancestorBody = ancestorBody;
 837                 this.initTypes = initTypes;
 838                 this.init = init;
 839             }
 840 
 841             public JavaForOp.UpdateBuilder cond(Consumer<Block.Builder> c) {
 842                 Body.Builder cond = Body.Builder.of(ancestorBody,
 843                         FunctionType.functionType(BOOLEAN, initTypes));
 844                 c.accept(cond.entryBlock());
 845 
 846                 return new UpdateBuilder(ancestorBody, initTypes, init, cond);
 847             }
 848         }
 849 
 850         public static final class UpdateBuilder {
 851             final Body.Builder ancestorBody;
 852             final List<? extends TypeElement> initTypes;
 853             final Body.Builder init;
 854             final Body.Builder cond;
 855 
 856             public UpdateBuilder(Body.Builder ancestorBody,
 857                                  List<? extends TypeElement> initTypes,
 858                                  Body.Builder init, Body.Builder cond) {
 859                 this.ancestorBody = ancestorBody;
 860                 this.initTypes = initTypes;
 861                 this.init = init;
 862                 this.cond = cond;
 863             }
 864 
 865             public JavaForOp.BodyBuilder cond(Consumer<Block.Builder> c) {
 866                 Body.Builder update = Body.Builder.of(ancestorBody,
 867                         FunctionType.functionType(VOID, initTypes));
 868                 c.accept(update.entryBlock());
 869 
 870                 return new BodyBuilder(ancestorBody, initTypes, init, cond, update);
 871             }
 872 
 873         }
 874 
 875         public static final class BodyBuilder {
 876             final Body.Builder ancestorBody;
 877             final List<? extends TypeElement> initTypes;
 878             final Body.Builder init;
 879             final Body.Builder cond;
 880             final Body.Builder update;
 881 
 882             public BodyBuilder(Body.Builder ancestorBody,
 883                                List<? extends TypeElement> initTypes,
 884                                Body.Builder init, Body.Builder cond, Body.Builder update) {
 885                 this.ancestorBody = ancestorBody;
 886                 this.initTypes = initTypes;
 887                 this.init = init;
 888                 this.cond = cond;
 889                 this.update = update;
 890             }
 891 
 892             public JavaForOp body(Consumer<Block.Builder> c) {
 893                 Body.Builder body = Body.Builder.of(ancestorBody,
 894                         FunctionType.functionType(VOID, initTypes));
 895                 c.accept(body.entryBlock());
 896 
 897                 return new JavaForOp(init, cond, update, body);
 898             }
 899         }
 900 
 901         static final String NAME = "java.for";
 902 
 903         final Body init;
 904         final Body cond;
 905         final Body update;
 906         final Body body;
 907 
 908         public static JavaForOp create(ExternalizedOp def) {
 909             return new JavaForOp(def);
 910         }
 911 
 912         public JavaForOp(ExternalizedOp def) {
 913             super(def);
 914 
 915             this.init = def.bodyDefinitions().get(0).build(this);
 916             this.cond = def.bodyDefinitions().get(1).build(this);
 917             this.update = def.bodyDefinitions().get(2).build(this);
 918             this.body = def.bodyDefinitions().get(3).build(this);
 919         }
 920 
 921         JavaForOp(JavaForOp that, CopyContext cc, OpTransformer ot) {
 922             super(that, cc);
 923 
 924             this.init = that.init.transform(cc, ot).build(this);
 925             this.cond = that.cond.transform(cc, ot).build(this);
 926             this.update = that.update.transform(cc, ot).build(this);
 927             this.body = that.body.transform(cc, ot).build(this);
 928         }
 929 
 930         @Override
 931         public JavaForOp transform(CopyContext cc, OpTransformer ot) {
 932             return new JavaForOp(this, cc, ot);
 933         }
 934 
 935         JavaForOp(Body.Builder initC,
 936                   Body.Builder condC,
 937                   Body.Builder updateC,
 938                   Body.Builder bodyC) {
 939             super(NAME, List.of());
 940 
 941             this.init = initC.build(this);
 942 
 943             this.cond = condC.build(this);
 944 
 945             this.update = updateC.build(this);
 946             if (!update.bodyType().returnType().equals(VOID)) {
 947                 throw new IllegalArgumentException("Update should return void: " + update.bodyType());
 948             }
 949 
 950             this.body = bodyC.build(this);
 951             if (!body.bodyType().returnType().equals(VOID)) {
 952                 throw new IllegalArgumentException("Body should return void: " + body.bodyType());
 953             }
 954         }
 955 
 956         @Override
 957         public List<Body> bodies() {
 958             return List.of(init, cond, update, body);
 959         }
 960 
 961         public Body init() {
 962             return init;
 963         }
 964 
 965         public Body cond() {
 966             return cond;
 967         }
 968 
 969         public Body update() {
 970             return update;
 971         }
 972 
 973         @Override
 974         public Body loopBody() {
 975             return body;
 976         }
 977 
 978         @Override
 979         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
 980             Block.Builder header = b.block();
 981             Block.Builder body = b.block();
 982             Block.Builder update = b.block();
 983             Block.Builder exit = b.block();
 984 
 985             List<Value> initValues = new ArrayList<>();
 986             // @@@ Init body has one yield operation yielding
 987             //  void, a single variable, or a tuple of one or more variables
 988             b.transformBody(init, List.of(), opT.andThen((block, op) -> {
 989                 if (op instanceof CoreOp.TupleOp) {
 990                     // Drop Tuple if a yielded
 991                     boolean isResult = op.result().uses().size() == 1 &&
 992                             op.result().uses().stream().allMatch(r -> r.op() instanceof YieldOp);
 993                     if (!isResult) {
 994                         block.op(op);
 995                     }
 996                 } else if (op instanceof YieldOp yop) {
 997                     if (yop.yieldValue() == null) {
 998                         block.op(branch(header.successor()));
 999                         return block;
1000                     } else if (yop.yieldValue() instanceof Result or) {
1001                         if (or.op() instanceof CoreOp.TupleOp top) {
1002                             initValues.addAll(block.context().getValues(top.operands()));
1003                         } else {
1004                             initValues.addAll(block.context().getValues(yop.operands()));
1005                         }
1006                         block.op(branch(header.successor()));
1007                         return block;
1008                     }
1009 
1010                     throw new IllegalStateException("Bad yield operation");
1011                 } else {
1012                     // @@@ Composition of lowerable ops
1013                     block.op(op);
1014                 }
1015                 return block;
1016             }));
1017 
1018             header.transformBody(cond, initValues, opT.andThen((block, op) -> {
1019                 if (op instanceof YieldOp yo) {
1020                     block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
1021                             body.successor(), exit.successor()));
1022                 } else if (op instanceof Lowerable lop) {
1023                     // @@@ Composition of lowerable ops
1024                     block = lop.lower(block, opT);
1025                 } else {
1026                     block.op(op);
1027                 }
1028                 return block;
1029             }));
1030 
1031             setBranchTarget(b.context(), this, new BranchTarget(exit, update));
1032 
1033             body.transformBody(this.body, initValues, opT.andThen((block, op) -> {
1034                 // @@@ Composition of lowerable ops
1035                 if (op instanceof Lowerable lop) {
1036                     block = lop.lower(block, opT);
1037                 } else {
1038                     block.op(op);
1039                 }
1040                 return block;
1041             }));
1042 
1043             update.transformBody(this.update, initValues, opT.andThen((block, op) -> {
1044                 if (op instanceof YieldOp) {
1045                     block.op(branch(header.successor()));
1046                 } else {
1047                     // @@@ Composition of lowerable ops
1048                     block.op(op);
1049                 }
1050                 return block;
1051             }));
1052 
1053             return exit;
1054         }
1055 
1056         @Override
1057         public TypeElement resultType() {
1058             return VOID;
1059         }
1060     }
1061 
1062     /**
1063      * The enhanced for operation, that can model a Java language enhanced for statement.
1064      */
1065     @OpFactory.OpDeclaration(JavaEnhancedForOp.NAME)
1066     public static final class JavaEnhancedForOp extends ExtendedOp
1067             implements Op.Loop, Op.Lowerable, JavaStatement {
1068 
1069         public static final class ExpressionBuilder {
1070             final Body.Builder ancestorBody;
1071             final TypeElement iterableType;
1072             final TypeElement elementType;
1073 
1074             ExpressionBuilder(Body.Builder ancestorBody,
1075                               TypeElement iterableType, TypeElement elementType) {
1076                 this.ancestorBody = ancestorBody;
1077                 this.iterableType = iterableType;
1078                 this.elementType = elementType;
1079             }
1080 
1081             public DefinitionBuilder expression(Consumer<Block.Builder> c) {
1082                 Body.Builder expression = Body.Builder.of(ancestorBody,
1083                         FunctionType.functionType(iterableType));
1084                 c.accept(expression.entryBlock());
1085 
1086                 return new DefinitionBuilder(ancestorBody, elementType, expression);
1087             }
1088         }
1089 
1090         public static final class DefinitionBuilder {
1091             final Body.Builder ancestorBody;
1092             final TypeElement elementType;
1093             final Body.Builder expression;
1094 
1095             DefinitionBuilder(Body.Builder ancestorBody,
1096                               TypeElement elementType, Body.Builder expression) {
1097                 this.ancestorBody = ancestorBody;
1098                 this.elementType = elementType;
1099                 this.expression = expression;
1100             }
1101 
1102             public BodyBuilder definition(Consumer<Block.Builder> c) {
1103                 return definition(elementType, c);
1104             }
1105 
1106             public BodyBuilder definition(TypeElement bodyElementType, Consumer<Block.Builder> c) {
1107                 Body.Builder definition = Body.Builder.of(ancestorBody,
1108                         FunctionType.functionType(bodyElementType, elementType));
1109                 c.accept(definition.entryBlock());
1110 
1111                 return new BodyBuilder(ancestorBody, elementType, expression, definition);
1112             }
1113         }
1114 
1115         public static final class BodyBuilder {
1116             final Body.Builder ancestorBody;
1117             final TypeElement elementType;
1118             final Body.Builder expression;
1119             final Body.Builder definition;
1120 
1121             BodyBuilder(Body.Builder ancestorBody,
1122                         TypeElement elementType, Body.Builder expression, Body.Builder definition) {
1123                 this.ancestorBody = ancestorBody;
1124                 this.elementType = elementType;
1125                 this.expression = expression;
1126                 this.definition = definition;
1127             }
1128 
1129             public JavaEnhancedForOp body(Consumer<Block.Builder> c) {
1130                 Body.Builder body = Body.Builder.of(ancestorBody,
1131                         FunctionType.functionType(VOID, elementType));
1132                 c.accept(body.entryBlock());
1133 
1134                 return new JavaEnhancedForOp(expression, definition, body);
1135             }
1136         }
1137 
1138         static final String NAME = "java.enhancedFor";
1139 
1140         final Body expression;
1141         final Body init;
1142         final Body body;
1143 
1144         public static JavaEnhancedForOp create(ExternalizedOp def) {
1145             return new JavaEnhancedForOp(def);
1146         }
1147 
1148         public JavaEnhancedForOp(ExternalizedOp def) {
1149             super(def);
1150 
1151             this.expression = def.bodyDefinitions().get(0).build(this);
1152             this.init = def.bodyDefinitions().get(1).build(this);
1153             this.body = def.bodyDefinitions().get(2).build(this);
1154         }
1155 
1156         JavaEnhancedForOp(JavaEnhancedForOp that, CopyContext cc, OpTransformer ot) {
1157             super(that, cc);
1158 
1159             this.expression = that.expression.transform(cc, ot).build(this);
1160             this.init = that.init.transform(cc, ot).build(this);
1161             this.body = that.body.transform(cc, ot).build(this);
1162         }
1163 
1164         @Override
1165         public JavaEnhancedForOp transform(CopyContext cc, OpTransformer ot) {
1166             return new JavaEnhancedForOp(this, cc, ot);
1167         }
1168 
1169         JavaEnhancedForOp(Body.Builder expressionC, Body.Builder initC, Body.Builder bodyC) {
1170             super(NAME, List.of());
1171 
1172             this.expression = expressionC.build(this);
1173             if (expression.bodyType().returnType().equals(VOID)) {
1174                 throw new IllegalArgumentException("Expression should return non-void value: " + expression.bodyType());
1175             }
1176             if (!expression.bodyType().parameterTypes().isEmpty()) {
1177                 throw new IllegalArgumentException("Expression should have zero parameters: " + expression.bodyType());
1178             }
1179 
1180             this.init = initC.build(this);
1181             if (init.bodyType().returnType().equals(VOID)) {
1182                 throw new IllegalArgumentException("Initialization should return non-void value: " + init.bodyType());
1183             }
1184             if (init.bodyType().parameterTypes().size() != 1) {
1185                 throw new IllegalArgumentException("Initialization should have one parameter: " + init.bodyType());
1186             }
1187 
1188             this.body = bodyC.build(this);
1189             if (!body.bodyType().returnType().equals(VOID)) {
1190                 throw new IllegalArgumentException("Body should return void: " + body.bodyType());
1191             }
1192             if (body.bodyType().parameterTypes().size() != 1) {
1193                 throw new IllegalArgumentException("Body should have one parameter: " + body.bodyType());
1194             }
1195         }
1196 
1197         @Override
1198         public List<Body> bodies() {
1199             return List.of(expression, init, body);
1200         }
1201 
1202         public Body expression() {
1203             return expression;
1204         }
1205 
1206         public Body initialization() {
1207             return init;
1208         }
1209 
1210         @Override
1211         public Body loopBody() {
1212             return body;
1213         }
1214 
1215         static final MethodRef ITERABLE_ITERATOR = MethodRef.method(Iterable.class, "iterator", Iterator.class);
1216         static final MethodRef ITERATOR_HAS_NEXT = MethodRef.method(Iterator.class, "hasNext", boolean.class);
1217         static final MethodRef ITERATOR_NEXT = MethodRef.method(Iterator.class, "next", Object.class);
1218 
1219         @Override
1220         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
1221             JavaType elementType = (JavaType) init.entryBlock().parameters().get(0).type();
1222             boolean isArray = expression.bodyType().returnType() instanceof ArrayType;
1223 
1224             Block.Builder preHeader = b.block(expression.bodyType().returnType());
1225             Block.Builder header = b.block(isArray ? List.of(INT) : List.of());
1226             Block.Builder init = b.block();
1227             Block.Builder body = b.block();
1228             Block.Builder exit = b.block();
1229 
1230             b.transformBody(expression, List.of(), opT.andThen((block, op) -> {
1231                 if (op instanceof YieldOp yop) {
1232                     Value loopSource = block.context().getValue(yop.yieldValue());
1233                     block.op(branch(preHeader.successor(loopSource)));
1234                 } else {
1235                     // @@@ Composition of lowerable ops
1236                     block.op(op);
1237                 }
1238                 return block;
1239             }));
1240 
1241             if (isArray) {
1242                 Value array = preHeader.parameters().get(0);
1243                 Value arrayLength = preHeader.op(arrayLength(array));
1244                 Value i = preHeader.op(constant(INT, 0));
1245                 preHeader.op(branch(header.successor(i)));
1246 
1247                 i = header.parameters().get(0);
1248                 Value p = header.op(lt(i, arrayLength));
1249                 header.op(conditionalBranch(p, init.successor(), exit.successor()));
1250 
1251                 Value e = init.op(arrayLoadOp(array, i));
1252                 List<Value> initValues = new ArrayList<>();
1253                 // @@@ Init body has one yield operation yielding a single variable
1254                 init.transformBody(this.init, List.of(e), (block, op) -> {
1255                     if (op instanceof YieldOp yop) {
1256                         initValues.addAll(block.context().getValues(yop.operands()));
1257                         block.op(branch(body.successor()));
1258                     } else {
1259                         // @@@ Composition of lowerable ops
1260                         block.op(op);
1261                     }
1262                     return block;
1263                 });
1264 
1265                 Block.Builder update = b.block();
1266                 setBranchTarget(b.context(), this, new BranchTarget(exit, update));
1267 
1268                 body.transformBody(this.body, initValues, opT.andThen((block, op) -> {
1269                     // @@@ Composition of lowerable ops
1270                     if (op instanceof Lowerable lop) {
1271                         block = lop.lower(block, opT);
1272                     } else {
1273                         block.op(op);
1274                     }
1275                     return block;
1276                 }));
1277 
1278                 i = update.op(add(i, update.op(constant(INT, 1))));
1279                 update.op(branch(header.successor(i)));
1280             } else {
1281                 JavaType iterable = parameterized(type(Iterator.class), elementType);
1282                 Value iterator = preHeader.op(CoreOp.invoke(iterable, ITERABLE_ITERATOR, preHeader.parameters().get(0)));
1283                 preHeader.op(branch(header.successor()));
1284 
1285                 Value p = header.op(CoreOp.invoke(ITERATOR_HAS_NEXT, iterator));
1286                 header.op(conditionalBranch(p, init.successor(), exit.successor()));
1287 
1288                 Value e = init.op(CoreOp.invoke(elementType, ITERATOR_NEXT, iterator));
1289                 List<Value> initValues = new ArrayList<>();
1290                 init.transformBody(this.init, List.of(e), opT.andThen((block, op) -> {
1291                     if (op instanceof YieldOp yop) {
1292                         initValues.addAll(block.context().getValues(yop.operands()));
1293                         block.op(branch(body.successor()));
1294                     } else {
1295                         // @@@ Composition of lowerable ops
1296                         block.op(op);
1297                     }
1298                     return block;
1299                 }));
1300 
1301                 setBranchTarget(b.context(), this, new BranchTarget(exit, header));
1302 
1303                 body.transformBody(this.body, initValues, opT.andThen((block, op) -> {
1304                     // @@@ Composition of lowerable ops
1305                     if (op instanceof Lowerable lop) {
1306                         block = lop.lower(block, opT);
1307                     } else {
1308                         block.op(op);
1309                     }
1310                     return block;
1311                 }));
1312             }
1313 
1314             return exit;
1315         }
1316 
1317         @Override
1318         public TypeElement resultType() {
1319             return VOID;
1320         }
1321     }
1322 
1323     /**
1324      * The while operation, that can model a Java language while statement.
1325      */
1326     @OpFactory.OpDeclaration(JavaWhileOp.NAME)
1327     public static final class JavaWhileOp extends ExtendedOp
1328             implements Op.Loop, Op.Lowerable, JavaStatement {
1329 
1330         public static class PredicateBuilder {
1331             final Body.Builder ancestorBody;
1332 
1333             PredicateBuilder(Body.Builder ancestorBody) {
1334                 this.ancestorBody = ancestorBody;
1335             }
1336 
1337             public JavaWhileOp.BodyBuilder predicate(Consumer<Block.Builder> c) {
1338                 Body.Builder body = Body.Builder.of(ancestorBody, FunctionType.functionType(BOOLEAN));
1339                 c.accept(body.entryBlock());
1340 
1341                 return new JavaWhileOp.BodyBuilder(ancestorBody, body);
1342             }
1343         }
1344 
1345         public static class BodyBuilder {
1346             final Body.Builder ancestorBody;
1347             private final Body.Builder predicate;
1348 
1349             BodyBuilder(Body.Builder ancestorBody, Body.Builder predicate) {
1350                 this.ancestorBody = ancestorBody;
1351                 this.predicate = predicate;
1352             }
1353 
1354             public JavaWhileOp body(Consumer<Block.Builder> c) {
1355                 Body.Builder body = Body.Builder.of(ancestorBody, FunctionType.VOID);
1356                 c.accept(body.entryBlock());
1357 
1358                 return new JavaWhileOp(List.of(predicate, body));
1359             }
1360         }
1361 
1362         private static final String NAME = "java.while";
1363 
1364         private final List<Body> bodies;
1365 
1366         public JavaWhileOp(ExternalizedOp def) {
1367             super(def);
1368 
1369             // @@@ Validate
1370             this.bodies = def.bodyDefinitions().stream().map(bd -> bd.build(this)).toList();
1371         }
1372 
1373         JavaWhileOp(List<Body.Builder> bodyCs) {
1374             super(NAME, List.of());
1375 
1376             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
1377         }
1378 
1379         JavaWhileOp(Body.Builder predicate, Body.Builder body) {
1380             super(NAME, List.of());
1381 
1382             Objects.requireNonNull(body);
1383 
1384             this.bodies = Stream.of(predicate, body).filter(Objects::nonNull)
1385                     .map(bc -> bc.build(this)).toList();
1386 
1387             // @@@ This will change with pattern bindings
1388             if (!bodies.get(0).bodyType().equals(FunctionType.functionType(BOOLEAN))) {
1389                 throw new IllegalArgumentException(
1390                         "Predicate body descriptor should be " + FunctionType.functionType(BOOLEAN) +
1391                                 " but is " + bodies.get(0).bodyType());
1392             }
1393             if (!bodies.get(1).bodyType().equals(FunctionType.VOID)) {
1394                 throw new IllegalArgumentException(
1395                         "Body descriptor should be " + FunctionType.functionType(VOID) +
1396                                 " but is " + bodies.get(1).bodyType());
1397             }
1398         }
1399 
1400         JavaWhileOp(JavaWhileOp that, CopyContext cc, OpTransformer ot) {
1401             super(that, cc);
1402 
1403             this.bodies = that.bodies.stream()
1404                     .map(b -> b.transform(cc, ot).build(this)).toList();
1405         }
1406 
1407         @Override
1408         public JavaWhileOp transform(CopyContext cc, OpTransformer ot) {
1409             return new JavaWhileOp(this, cc, ot);
1410         }
1411 
1412         @Override
1413         public List<Body> bodies() {
1414             return bodies;
1415         }
1416 
1417         public Body predicateBody() {
1418             return bodies.get(0);
1419         }
1420 
1421         @Override
1422         public Body loopBody() {
1423             return bodies.get(1);
1424         }
1425 
1426         @Override
1427         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
1428             Block.Builder header = b.block();
1429             Block.Builder body = b.block();
1430             Block.Builder exit = b.block();
1431 
1432             b.op(branch(header.successor()));
1433 
1434             header.transformBody(predicateBody(), List.of(), opT.andThen((block, op) -> {
1435                 if (op instanceof CoreOp.YieldOp yo) {
1436                     block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
1437                             body.successor(), exit.successor()));
1438                 } else if (op instanceof Lowerable lop) {
1439                     // @@@ Composition of lowerable ops
1440                     block = lop.lower(block, opT);
1441                 } else {
1442                     block.op(op);
1443                 }
1444                 return block;
1445             }));
1446 
1447             setBranchTarget(b.context(), this, new BranchTarget(exit, header));
1448 
1449             body.transformBody(loopBody(), List.of(), opT.andThen((block, op) -> {
1450                 // @@@ Composition of lowerable ops
1451                 if (op instanceof Lowerable lop) {
1452                     block = lop.lower(block, opT);
1453                 } else {
1454                     block.op(op);
1455                 }
1456                 return block;
1457             }));
1458 
1459             return exit;
1460         }
1461 
1462         @Override
1463         public TypeElement resultType() {
1464             return VOID;
1465         }
1466     }
1467 
1468     /**
1469      * The do-while operation, that can model a Java language do statement.
1470      */
1471     // @@@ Unify JavaDoWhileOp and JavaWhileOp with common abstract superclass
1472     @OpFactory.OpDeclaration(JavaDoWhileOp.NAME)
1473     public static final class JavaDoWhileOp extends ExtendedOp
1474             implements Op.Loop, Op.Lowerable, JavaStatement {
1475 
1476         public static class PredicateBuilder {
1477             final Body.Builder ancestorBody;
1478             private final Body.Builder body;
1479 
1480             PredicateBuilder(Body.Builder ancestorBody, Body.Builder body) {
1481                 this.ancestorBody = ancestorBody;
1482                 this.body = body;
1483             }
1484 
1485             public JavaDoWhileOp predicate(Consumer<Block.Builder> c) {
1486                 Body.Builder predicate = Body.Builder.of(ancestorBody, FunctionType.functionType(BOOLEAN));
1487                 c.accept(predicate.entryBlock());
1488 
1489                 return new JavaDoWhileOp(List.of(body, predicate));
1490             }
1491         }
1492 
1493         public static class BodyBuilder {
1494             final Body.Builder ancestorBody;
1495 
1496             BodyBuilder(Body.Builder ancestorBody) {
1497                 this.ancestorBody = ancestorBody;
1498             }
1499 
1500             public JavaDoWhileOp.PredicateBuilder body(Consumer<Block.Builder> c) {
1501                 Body.Builder body = Body.Builder.of(ancestorBody, FunctionType.VOID);
1502                 c.accept(body.entryBlock());
1503 
1504                 return new JavaDoWhileOp.PredicateBuilder(ancestorBody, body);
1505             }
1506         }
1507 
1508         private static final String NAME = "java.do.while";
1509 
1510         private final List<Body> bodies;
1511 
1512         public JavaDoWhileOp(ExternalizedOp def) {
1513             super(def);
1514 
1515             // @@@ Validate
1516             this.bodies = def.bodyDefinitions().stream().map(bd -> bd.build(this)).toList();
1517         }
1518 
1519         JavaDoWhileOp(List<Body.Builder> bodyCs) {
1520             super(NAME, List.of());
1521 
1522             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
1523         }
1524 
1525         JavaDoWhileOp(Body.Builder body, Body.Builder predicate) {
1526             super(NAME, List.of());
1527 
1528             Objects.requireNonNull(body);
1529 
1530             this.bodies = Stream.of(body, predicate).filter(Objects::nonNull)
1531                     .map(bc -> bc.build(this)).toList();
1532 
1533             if (!bodies.get(0).bodyType().equals(FunctionType.VOID)) {
1534                 throw new IllegalArgumentException(
1535                         "Body descriptor should be " + FunctionType.functionType(VOID) +
1536                                 " but is " + bodies.get(1).bodyType());
1537             }
1538             if (!bodies.get(1).bodyType().equals(FunctionType.functionType(BOOLEAN))) {
1539                 throw new IllegalArgumentException(
1540                         "Predicate body descriptor should be " + FunctionType.functionType(BOOLEAN) +
1541                                 " but is " + bodies.get(0).bodyType());
1542             }
1543         }
1544 
1545         JavaDoWhileOp(JavaDoWhileOp that, CopyContext cc, OpTransformer ot) {
1546             super(that, cc);
1547 
1548             this.bodies = that.bodies.stream()
1549                     .map(b -> b.transform(cc, ot).build(this)).toList();
1550         }
1551 
1552         @Override
1553         public JavaDoWhileOp transform(CopyContext cc, OpTransformer ot) {
1554             return new JavaDoWhileOp(this, cc, ot);
1555         }
1556 
1557         @Override
1558         public List<Body> bodies() {
1559             return bodies;
1560         }
1561 
1562         public Body predicateBody() {
1563             return bodies.get(1);
1564         }
1565 
1566         @Override
1567         public Body loopBody() {
1568             return bodies.get(0);
1569         }
1570 
1571         @Override
1572         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
1573             Block.Builder body = b.block();
1574             Block.Builder header = b.block();
1575             Block.Builder exit = b.block();
1576 
1577             b.op(branch(body.successor()));
1578 
1579             setBranchTarget(b.context(), this, new BranchTarget(exit, header));
1580 
1581             body.transformBody(loopBody(), List.of(), opT.andThen((block, op) -> {
1582                 // @@@ Composition of lowerable ops
1583                 if (op instanceof Lowerable lop) {
1584                     block = lop.lower(block, opT);
1585                 } else {
1586                     block.op(op);
1587                 }
1588                 return block;
1589             }));
1590 
1591             header.transformBody(predicateBody(), List.of(), opT.andThen((block, op) -> {
1592                 if (op instanceof CoreOp.YieldOp yo) {
1593                     block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
1594                             body.successor(), exit.successor()));
1595                 } else if (op instanceof Lowerable lop) {
1596                     // @@@ Composition of lowerable ops
1597                     block = lop.lower(block, opT);
1598                 } else {
1599                     block.op(op);
1600                 }
1601                 return block;
1602             }));
1603 
1604             return exit;
1605         }
1606 
1607         @Override
1608         public TypeElement resultType() {
1609             return VOID;
1610         }
1611     }
1612 
1613     /**
1614      * The conditional-and-or operation, that can model Java language condition-or or conditional-and expressions.
1615      */
1616     public sealed static abstract class JavaConditionalOp extends ExtendedOp
1617             implements Op.Nested, Op.Lowerable, JavaExpression {
1618         final List<Body> bodies;
1619 
1620         public JavaConditionalOp(ExternalizedOp def) {
1621             super(def);
1622 
1623             if (!def.operands().isEmpty()) {
1624                 throw new IllegalStateException("Operation must have no operands");
1625             }
1626 
1627             // @@@ Validate
1628 
1629             this.bodies = def.bodyDefinitions().stream().map(bd -> bd.build(this)).toList();
1630         }
1631 
1632         JavaConditionalOp(JavaConditionalOp that, CopyContext cc, OpTransformer ot) {
1633             super(that, cc);
1634 
1635             // Copy body
1636             this.bodies = that.bodies.stream().map(b -> b.transform(cc, ot).build(this)).toList();
1637         }
1638 
1639         JavaConditionalOp(String name, List<Body.Builder> bodyCs) {
1640             super(name, List.of());
1641 
1642             if (bodyCs.isEmpty()) {
1643                 throw new IllegalArgumentException();
1644             }
1645 
1646             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
1647             for (Body b : bodies) {
1648                 if (!b.bodyType().equals(FunctionType.functionType(BOOLEAN))) {
1649                     throw new IllegalArgumentException("Body conditional body descriptor: " + b.bodyType());
1650                 }
1651             }
1652         }
1653 
1654         @Override
1655         public List<Body> bodies() {
1656             return bodies;
1657         }
1658 
1659         static Block.Builder lower(Block.Builder startBlock, OpTransformer opT, JavaConditionalOp cop) {
1660             List<Body> bodies = cop.bodies();
1661 
1662             Block.Builder exit = startBlock.block();
1663             TypeElement oprType = cop.result().type();
1664             Block.Parameter arg = exit.parameter(oprType);
1665             startBlock.context().mapValue(cop.result(), arg);
1666 
1667             // Transform bodies in reverse order
1668             // This makes available the blocks to be referenced as successors in prior blocks
1669 
1670             Block.Builder pred = null;
1671             for (int i = bodies.size() - 1; i >= 0; i--) {
1672                 OpTransformer opt;
1673                 if (i == bodies.size() - 1) {
1674                     opt = (block, op) -> {
1675                         if (op instanceof CoreOp.YieldOp yop) {
1676                             Value p = block.context().getValue(yop.yieldValue());
1677                             block.op(branch(exit.successor(p)));
1678                         } else if (op instanceof Lowerable lop) {
1679                             // @@@ Composition of lowerable ops
1680                             block = lop.lower(block, opT);
1681                         } else {
1682                             // Copy
1683                             block.apply(op);
1684                         }
1685                         return block;
1686                     };
1687                 } else {
1688                     Block.Builder nextPred = pred;
1689                     opt = (block, op) -> {
1690                         if (op instanceof CoreOp.YieldOp yop) {
1691                             Value p = block.context().getValue(yop.yieldValue());
1692                             if (cop instanceof JavaConditionalAndOp) {
1693                                 block.op(conditionalBranch(p, nextPred.successor(), exit.successor(p)));
1694                             } else {
1695                                 block.op(conditionalBranch(p, exit.successor(p), nextPred.successor()));
1696                             }
1697                         } else if (op instanceof Lowerable lop) {
1698                             // @@@ Composition of lowerable ops
1699                             block = lop.lower(block, opT);
1700                         } else {
1701                             // Copy
1702                             block.apply(op);
1703                         }
1704                         return block;
1705                     };
1706                 }
1707 
1708                 Body fromPred = bodies.get(i);
1709                 if (i == 0) {
1710                     startBlock.transformBody(fromPred, List.of(), opt);
1711                 } else {
1712                     pred = startBlock.block(fromPred.bodyType().parameterTypes());
1713                     pred.transformBody(fromPred, pred.parameters(), opT.andThen(opt));
1714                 }
1715             }
1716 
1717             return exit;
1718         }
1719 
1720         @Override
1721         public TypeElement resultType() {
1722             return BOOLEAN;
1723         }
1724     }
1725 
1726     /**
1727      * The conditional-and operation, that can model Java language conditional-and expressions.
1728      */
1729     @OpFactory.OpDeclaration(JavaConditionalAndOp.NAME)
1730     public static final class JavaConditionalAndOp extends JavaConditionalOp {
1731 
1732         public static class Builder {
1733             final Body.Builder ancestorBody;
1734             final List<Body.Builder> bodies;
1735 
1736             Builder(Body.Builder ancestorBody, Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
1737                 this.ancestorBody = ancestorBody;
1738                 this.bodies = new ArrayList<>();
1739                 and(lhs);
1740                 and(rhs);
1741             }
1742 
1743             public Builder and(Consumer<Block.Builder> c) {
1744                 Body.Builder body = Body.Builder.of(ancestorBody, FunctionType.functionType(BOOLEAN));
1745                 c.accept(body.entryBlock());
1746                 bodies.add(body);
1747 
1748                 return this;
1749             }
1750 
1751             public JavaConditionalAndOp build() {
1752                 return new JavaConditionalAndOp(bodies);
1753             }
1754         }
1755 
1756         public static final String NAME = "java.cand";
1757 
1758         public JavaConditionalAndOp(ExternalizedOp def) {
1759             super(def);
1760         }
1761 
1762         JavaConditionalAndOp(JavaConditionalAndOp that, CopyContext cc, OpTransformer ot) {
1763             super(that, cc, ot);
1764         }
1765 
1766         @Override
1767         public JavaConditionalAndOp transform(CopyContext cc, OpTransformer ot) {
1768             return new JavaConditionalAndOp(this, cc, ot);
1769         }
1770 
1771         JavaConditionalAndOp(List<Body.Builder> bodyCs) {
1772             super(NAME, bodyCs);
1773         }
1774 
1775         @Override
1776         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
1777             return lower(b, opT, this);
1778         }
1779     }
1780 
1781     /**
1782      * The conditional-or operation, that can model Java language conditional-or expressions.
1783      */
1784     @OpFactory.OpDeclaration(JavaConditionalOrOp.NAME)
1785     public static final class JavaConditionalOrOp extends JavaConditionalOp {
1786 
1787         public static class Builder {
1788             final Body.Builder ancestorBody;
1789             final List<Body.Builder> bodies;
1790 
1791             Builder(Body.Builder ancestorBody, Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
1792                 this.ancestorBody = ancestorBody;
1793                 this.bodies = new ArrayList<>();
1794                 or(lhs);
1795                 or(rhs);
1796             }
1797 
1798             public Builder or(Consumer<Block.Builder> c) {
1799                 Body.Builder body = Body.Builder.of(ancestorBody, FunctionType.functionType(BOOLEAN));
1800                 c.accept(body.entryBlock());
1801                 bodies.add(body);
1802 
1803                 return this;
1804             }
1805 
1806             public JavaConditionalOrOp build() {
1807                 return new JavaConditionalOrOp(bodies);
1808             }
1809         }
1810 
1811         public static final String NAME = "java.cor";
1812 
1813         public JavaConditionalOrOp(ExternalizedOp def) {
1814             super(def);
1815         }
1816 
1817         JavaConditionalOrOp(JavaConditionalOrOp that, CopyContext cc, OpTransformer ot) {
1818             super(that, cc, ot);
1819         }
1820 
1821         @Override
1822         public JavaConditionalOrOp transform(CopyContext cc, OpTransformer ot) {
1823             return new JavaConditionalOrOp(this, cc, ot);
1824         }
1825 
1826         JavaConditionalOrOp(List<Body.Builder> bodyCs) {
1827             super(NAME, bodyCs);
1828         }
1829 
1830         @Override
1831         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
1832             return lower(b, opT, this);
1833         }
1834     }
1835 
1836     /**
1837      * The conditional operation, that can model Java language conditional operator {@code ?} expressions.
1838      */
1839     @OpFactory.OpDeclaration(JavaConditionalExpressionOp.NAME)
1840     public static final class JavaConditionalExpressionOp extends ExtendedOp
1841             implements Op.Nested, Op.Lowerable, JavaExpression {
1842 
1843         public static final String NAME = "java.cexpression";
1844 
1845         final TypeElement resultType;
1846         // {cond, truepart, falsepart}
1847         final List<Body> bodies;
1848 
1849         public JavaConditionalExpressionOp(ExternalizedOp def) {
1850             super(def);
1851 
1852             if (!def.operands().isEmpty()) {
1853                 throw new IllegalStateException("Operation must have no operands");
1854             }
1855 
1856             // @@@ Validate
1857 
1858             this.bodies = def.bodyDefinitions().stream().map(bd -> bd.build(this)).toList();
1859             this.resultType = def.resultType();
1860         }
1861 
1862         JavaConditionalExpressionOp(JavaConditionalExpressionOp that, CopyContext cc, OpTransformer ot) {
1863             super(that, cc);
1864 
1865             // Copy body
1866             this.bodies = that.bodies.stream()
1867                     .map(b -> b.transform(cc, ot).build(this)).toList();
1868             this.resultType = that.resultType;
1869         }
1870 
1871         @Override
1872         public JavaConditionalExpressionOp transform(CopyContext cc, OpTransformer ot) {
1873             return new JavaConditionalExpressionOp(this, cc, ot);
1874         }
1875 
1876         JavaConditionalExpressionOp(TypeElement expressionType, List<Body.Builder> bodyCs) {
1877             super(NAME, List.of());
1878 
1879             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
1880             // @@@ when expressionType is null, we assume truepart and falsepart have the same yieldType
1881             this.resultType = expressionType == null ? bodies.get(1).yieldType() : expressionType;
1882 
1883             if (bodies.size() < 3) {
1884                 throw new IllegalArgumentException("Incorrect number of bodies: " + bodies.size());
1885             }
1886 
1887             Body cond = bodies.get(0);
1888             if (!cond.bodyType().equals(FunctionType.functionType(BOOLEAN))) {
1889                 throw new IllegalArgumentException("Illegal cond body descriptor: " + cond.bodyType());
1890             }
1891         }
1892 
1893         @Override
1894         public List<Body> bodies() {
1895             return bodies;
1896         }
1897 
1898         @Override
1899         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
1900             Block.Builder exit = b.block(resultType());
1901             exit.context().mapValue(result(), exit.parameters().get(0));
1902 
1903             setBranchTarget(b.context(), this, new BranchTarget(exit, null));
1904 
1905             List<Block.Builder> builders = List.of(b.block(), b.block());
1906             b.transformBody(bodies.get(0), List.of(), opT.andThen((block, op) -> {
1907                 if (op instanceof YieldOp yo) {
1908                     block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
1909                             builders.get(0).successor(), builders.get(1).successor()));
1910                 } else if (op instanceof Lowerable lop) {
1911                     // @@@ Composition of lowerable ops
1912                     block = lop.lower(block, opT);
1913                 } else {
1914                     block.op(op);
1915                 }
1916                 return block;
1917             }));
1918 
1919             for (int i = 0; i < 2; i++) {
1920                 builders.get(i).transformBody(bodies.get(i + 1), List.of(), opT.andThen((block, op) -> {
1921                     if (op instanceof YieldOp yop) {
1922                         block.op(branch(exit.successor(block.context().getValue(yop.yieldValue()))));
1923                     } else if (op instanceof Lowerable lop) {
1924                         // @@@ Composition of lowerable ops
1925                         block = lop.lower(block, opT);
1926                     } else {
1927                         block.op(op);
1928                     }
1929                     return block;
1930                 }));
1931             }
1932 
1933             return exit;
1934         }
1935 
1936         @Override
1937         public TypeElement resultType() {
1938             return resultType;
1939         }
1940     }
1941 
1942     /**
1943      * The try operation, that can model Java language try statements.
1944      */
1945     @OpFactory.OpDeclaration(JavaTryOp.NAME)
1946     public static final class JavaTryOp extends ExtendedOp
1947             implements Op.Nested, Op.Lowerable, JavaStatement {
1948 
1949         public static final class BodyBuilder {
1950             final Body.Builder ancestorBody;
1951             final List<? extends TypeElement> resourceTypes;
1952             final Body.Builder resources;
1953 
1954             BodyBuilder(Body.Builder ancestorBody, List<? extends TypeElement> resourceTypes, Body.Builder resources) {
1955                 this.ancestorBody = ancestorBody;
1956                 this.resourceTypes = resourceTypes;
1957                 this.resources = resources;
1958             }
1959 
1960             public CatchBuilder body(Consumer<Block.Builder> c) {
1961                 Body.Builder body = Body.Builder.of(ancestorBody,
1962                         FunctionType.functionType(VOID, resourceTypes));
1963                 c.accept(body.entryBlock());
1964 
1965                 return new CatchBuilder(ancestorBody, resources, body);
1966             }
1967         }
1968 
1969         public static final class CatchBuilder {
1970             final Body.Builder ancestorBody;
1971             final Body.Builder resources;
1972             final Body.Builder body;
1973             final List<Body.Builder> catchers;
1974 
1975             CatchBuilder(Body.Builder ancestorBody, Body.Builder resources, Body.Builder body) {
1976                 this.ancestorBody = ancestorBody;
1977                 this.resources = resources;
1978                 this.body = body;
1979                 this.catchers = new ArrayList<>();
1980             }
1981 
1982             // @@@ multi-catch
1983             public CatchBuilder _catch(TypeElement exceptionType, Consumer<Block.Builder> c) {
1984                 Body.Builder _catch = Body.Builder.of(ancestorBody,
1985                         FunctionType.functionType(VOID, exceptionType));
1986                 c.accept(_catch.entryBlock());
1987                 catchers.add(_catch);
1988 
1989                 return this;
1990             }
1991 
1992             public JavaTryOp _finally(Consumer<Block.Builder> c) {
1993                 Body.Builder _finally = Body.Builder.of(ancestorBody, FunctionType.VOID);
1994                 c.accept(_finally.entryBlock());
1995 
1996                 return new JavaTryOp(resources, body, catchers, _finally);
1997             }
1998 
1999             public JavaTryOp noFinalizer() {
2000                 return new JavaTryOp(resources, body, catchers, null);
2001             }
2002         }
2003 
2004         static final String NAME = "java.try";
2005 
2006         final Body resources;
2007         final Body body;
2008         final List<Body> catchers;
2009         final Body finalizer;
2010 
2011         public static JavaTryOp create(ExternalizedOp def) {
2012             return new JavaTryOp(def);
2013         }
2014 
2015         public JavaTryOp(ExternalizedOp def) {
2016             super(def);
2017 
2018             List<Body> bodies = def.bodyDefinitions().stream().map(b -> b.build(this)).toList();
2019             Body first = bodies.get(0);
2020             if (first.bodyType().returnType().equals(VOID)) {
2021                 this.resources = null;
2022                 this.body = first;
2023             } else {
2024                 this.resources = first;
2025                 this.body = bodies.get(1);
2026             }
2027 
2028             Body last = bodies.get(bodies.size() - 1);
2029             if (last.bodyType().parameterTypes().isEmpty()) {
2030                 this.finalizer = last;
2031             } else {
2032                 this.finalizer = null;
2033             }
2034             this.catchers = bodies.subList(
2035                     resources == null ? 1 : 2,
2036                     bodies.size() - (finalizer == null ? 0 : 1));
2037         }
2038 
2039         JavaTryOp(JavaTryOp that, CopyContext cc, OpTransformer ot) {
2040             super(that, cc);
2041 
2042             if (that.resources != null) {
2043                 this.resources = that.resources.transform(cc, ot).build(this);
2044             } else {
2045                 this.resources = null;
2046             }
2047             this.body = that.body.transform(cc, ot).build(this);
2048             this.catchers = that.catchers.stream()
2049                     .map(b -> b.transform(cc, ot).build(this))
2050                     .toList();
2051             if (that.finalizer != null) {
2052                 this.finalizer = that.finalizer.transform(cc, ot).build(this);
2053             } else {
2054                 this.finalizer = null;
2055             }
2056         }
2057 
2058         @Override
2059         public JavaTryOp transform(CopyContext cc, OpTransformer ot) {
2060             return new JavaTryOp(this, cc, ot);
2061         }
2062 
2063         JavaTryOp(Body.Builder resourcesC,
2064                   Body.Builder bodyC,
2065                   List<Body.Builder> catchersC,
2066                   Body.Builder finalizerC) {
2067             super(NAME, List.of());
2068 
2069             if (resourcesC != null) {
2070                 this.resources = resourcesC.build(this);
2071                 if (resources.bodyType().returnType().equals(VOID)) {
2072                     throw new IllegalArgumentException("Resources should not return void: " + resources.bodyType());
2073                 }
2074                 if (!resources.bodyType().parameterTypes().isEmpty()) {
2075                     throw new IllegalArgumentException("Resources should have zero parameters: " + resources.bodyType());
2076                 }
2077             } else {
2078                 this.resources = null;
2079             }
2080 
2081             this.body = bodyC.build(this);
2082             if (!body.bodyType().returnType().equals(VOID)) {
2083                 throw new IllegalArgumentException("Try should return void: " + body.bodyType());
2084             }
2085 
2086             this.catchers = catchersC.stream().map(c -> c.build(this)).toList();
2087             for (Body _catch : catchers) {
2088                 if (!_catch.bodyType().returnType().equals(VOID)) {
2089                     throw new IllegalArgumentException("Catch should return void: " + _catch.bodyType());
2090                 }
2091                 if (_catch.bodyType().parameterTypes().size() != 1) {
2092                     throw new IllegalArgumentException("Catch should have zero parameters: " + _catch.bodyType());
2093                 }
2094             }
2095 
2096             if (finalizerC != null) {
2097                 this.finalizer = finalizerC.build(this);
2098                 if (!finalizer.bodyType().returnType().equals(VOID)) {
2099                     throw new IllegalArgumentException("Finally should return void: " + finalizer.bodyType());
2100                 }
2101                 if (!finalizer.bodyType().parameterTypes().isEmpty()) {
2102                     throw new IllegalArgumentException("Finally should have zero parameters: " + finalizer.bodyType());
2103                 }
2104             } else {
2105                 this.finalizer = null;
2106             }
2107         }
2108 
2109         @Override
2110         public List<Body> bodies() {
2111             ArrayList<Body> bodies = new ArrayList<>();
2112             if (resources != null) {
2113                 bodies.add(resources);
2114             }
2115             bodies.add(body);
2116             bodies.addAll(catchers);
2117             if (finalizer != null) {
2118                 bodies.add(finalizer);
2119             }
2120             return bodies;
2121         }
2122 
2123         public Body resources() {
2124             return resources;
2125         }
2126 
2127         public Body body() {
2128             return body;
2129         }
2130 
2131         public List<Body> catchers() {
2132             return catchers;
2133         }
2134 
2135         public Body finalizer() {
2136             return finalizer;
2137         }
2138 
2139         @Override
2140         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
2141             if (resources != null) {
2142                 throw new UnsupportedOperationException("Lowering of try-with-resources is unsupported");
2143             }
2144 
2145             Block.Builder exit = b.block();
2146             setBranchTarget(b.context(), this, new BranchTarget(exit, null));
2147 
2148             // Simple case with no catch and finally bodies
2149             if (catchers.isEmpty() && finalizer == null) {
2150                 b.transformBody(body, List.of(), (block, op) -> {
2151                     if (op instanceof YieldOp) {
2152                         block.op(branch(exit.successor()));
2153                     } else {
2154                         // @@@ Composition of lowerable ops
2155                         if (op instanceof Lowerable lop) {
2156                             block = lop.lower(block, opT);
2157                         } else {
2158                             block.op(op);
2159                         }
2160                     }
2161                     return block;
2162                 });
2163                 return exit;
2164             }
2165 
2166             Block.Builder tryRegionEnter = b.block();
2167             Block.Builder tryRegionExit = b.block();
2168 
2169             // Construct the catcher block builders
2170             List<Block.Builder> catchers = catchers().stream()
2171                     .map(catcher -> b.block())
2172                     .toList();
2173             Block.Builder catcherFinally = null;
2174             if (finalizer != null) {
2175                 catcherFinally = b.block();
2176                 catchers = new ArrayList<>(catchers);
2177                 catchers.add(catcherFinally);
2178             }
2179 
2180             // Enter the try exception region
2181             Result tryExceptionRegion = b.op(exceptionRegionEnter(tryRegionEnter.successor(), catchers.stream()
2182                     .map(Block.Builder::successor)
2183                     .toList()));
2184 
2185             OpTransformer tryExitTransformer;
2186             if (finalizer != null) {
2187                 tryExitTransformer = opT.compose((block, op) -> {
2188                     if (op instanceof CoreOp.ReturnOp) {
2189                         return inlineFinalizer(block, tryExceptionRegion, opT);
2190                     } else if (op instanceof ExtendedOp.JavaLabelOp lop && ifExitFromTry(lop)) {
2191                         return inlineFinalizer(block, tryExceptionRegion, opT);
2192                     } else {
2193                         return block;
2194                     }
2195                 });
2196             } else {
2197                 tryExitTransformer = opT.compose((block, op) -> {
2198                     // @@@ break and continue
2199                     // when target break/continue is enclosing the try
2200                     if (op instanceof CoreOp.ReturnOp) {
2201                         Block.Builder tryRegionReturnExit = block.block();
2202                         block.op(exceptionRegionExit(tryExceptionRegion, tryRegionReturnExit.successor()));
2203                         return tryRegionReturnExit;
2204                     } else {
2205                         return block;
2206                     }
2207                 });
2208             }
2209             // Inline the try body
2210             AtomicBoolean hasTryRegionExit = new AtomicBoolean();
2211             tryRegionEnter.transformBody(body, List.of(), tryExitTransformer.andThen((block, op) -> {
2212                 if (op instanceof YieldOp) {
2213                     hasTryRegionExit.set(true);
2214                     block.op(branch(tryRegionExit.successor()));
2215                 } else {
2216                     // @@@ Composition of lowerable ops
2217                     if (op instanceof Lowerable lop) {
2218                         block = lop.lower(block, tryExitTransformer);
2219                     } else {
2220                         block.op(op);
2221                     }
2222                 }
2223                 return block;
2224             }));
2225 
2226             Block.Builder finallyEnter = null;
2227             if (finalizer != null) {
2228                 finallyEnter = b.block();
2229                 if (hasTryRegionExit.get()) {
2230                     // Exit the try exception region
2231                     tryRegionExit.op(exceptionRegionExit(tryExceptionRegion, finallyEnter.successor()));
2232                 }
2233             } else if (hasTryRegionExit.get()) {
2234                 // Exit the try exception region
2235                 tryRegionExit.op(exceptionRegionExit(tryExceptionRegion, exit.successor()));
2236             }
2237 
2238             // Inline the catch bodies
2239             for (int i = 0; i < this.catchers.size(); i++) {
2240                 Block.Builder catcher = catchers.get(i);
2241                 Body catcherBody = this.catchers.get(i);
2242                 // Create the throwable argument
2243                 Block.Parameter t = catcher.parameter(catcherBody.bodyType().parameterTypes().get(0));
2244 
2245                 if (finalizer != null) {
2246                     Block.Builder catchRegionEnter = b.block();
2247                     Block.Builder catchRegionExit = b.block();
2248 
2249                     // Enter the catch exception region
2250                     Result catchExceptionRegion = catcher.op(
2251                             exceptionRegionEnter(catchRegionEnter.successor(), catcherFinally.successor()));
2252 
2253                     OpTransformer catchExitTransformer = opT.compose((block, op) -> {
2254                         if (op instanceof CoreOp.ReturnOp) {
2255                             return inlineFinalizer(block, catchExceptionRegion, opT);
2256                         } else if (op instanceof ExtendedOp.JavaLabelOp lop && ifExitFromTry(lop)) {
2257                             return inlineFinalizer(block, catchExceptionRegion, opT);
2258                         } else {
2259                             return block;
2260                         }
2261                     });
2262                     // Inline the catch body
2263                     AtomicBoolean hasCatchRegionExit = new AtomicBoolean();
2264                     catchRegionEnter.transformBody(catcherBody, List.of(t), catchExitTransformer.andThen((block, op) -> {
2265                         if (op instanceof YieldOp) {
2266                             hasCatchRegionExit.set(true);
2267                             block.op(branch(catchRegionExit.successor()));
2268                         } else {
2269                             // @@@ Composition of lowerable ops
2270                             if (op instanceof Lowerable lop) {
2271                                 block = lop.lower(block, catchExitTransformer);
2272                             } else {
2273                                 block.op(op);
2274                             }
2275                         }
2276                         return block;
2277                     }));
2278 
2279                     // Exit the catch exception region
2280                     if (hasCatchRegionExit.get()) {
2281                         hasTryRegionExit.set(true);
2282                         catchRegionExit.op(exceptionRegionExit(catchExceptionRegion, finallyEnter.successor()));
2283                     }
2284                 } else {
2285                     // Inline the catch body
2286                     catcher.transformBody(catcherBody, List.of(t), opT.andThen((block, op) -> {
2287                         if (op instanceof YieldOp) {
2288                             block.op(branch(exit.successor()));
2289                         } else {
2290                             // @@@ Composition of lowerable ops
2291                             if (op instanceof Lowerable lop) {
2292                                 block = lop.lower(block, opT);
2293                             } else {
2294                                 block.op(op);
2295                             }
2296                         }
2297                         return block;
2298                     }));
2299                 }
2300             }
2301 
2302             if (finalizer != null && hasTryRegionExit.get()) {
2303                 // Inline the finally body
2304                 finallyEnter.transformBody(finalizer, List.of(), opT.andThen((block, op) -> {
2305                     if (op instanceof YieldOp) {
2306                         block.op(branch(exit.successor()));
2307                     } else {
2308                         // @@@ Composition of lowerable ops
2309                         if (op instanceof Lowerable lop) {
2310                             block = lop.lower(block, opT);
2311                         } else {
2312                             block.op(op);
2313                         }
2314                     }
2315                     return block;
2316                 }));
2317             }
2318 
2319             // Inline the finally body as a catcher of Throwable and adjusting to throw
2320             if (finalizer != null) {
2321                 // Create the throwable argument
2322                 Block.Parameter t = catcherFinally.parameter(type(Throwable.class));
2323 
2324                 catcherFinally.transformBody(finalizer, List.of(), opT.andThen((block, op) -> {
2325                     if (op instanceof YieldOp) {
2326                         block.op(_throw(t));
2327                     } else {
2328                         // @@@ Composition of lowerable ops
2329                         if (op instanceof Lowerable lop) {
2330                             block = lop.lower(block, opT);
2331                         } else {
2332                             block.op(op);
2333                         }
2334                     }
2335                     return block;
2336                 }));
2337             }
2338             return exit;
2339         }
2340 
2341         boolean ifExitFromTry(JavaLabelOp lop) {
2342             Op target = lop.target();
2343             return target == this || ifAncestorOp(target, this);
2344         }
2345 
2346         static boolean ifAncestorOp(Op ancestor, Op op) {
2347             while (op.ancestorBody() != null) {
2348                 op = op.ancestorBody().parentOp();
2349                 if (op == ancestor) {
2350                     return true;
2351                 }
2352             }
2353             return false;
2354         }
2355 
2356         Block.Builder inlineFinalizer(Block.Builder block1, Value exceptionRegion, OpTransformer opT) {
2357             Block.Builder finallyEnter = block1.block();
2358             Block.Builder finallyExit = block1.block();
2359 
2360             block1.op(exceptionRegionExit(exceptionRegion, finallyEnter.successor()));
2361 
2362             // Inline the finally body
2363             finallyEnter.transformBody(finalizer, List.of(), opT.andThen((block2, op2) -> {
2364                 if (op2 instanceof YieldOp) {
2365                     block2.op(branch(finallyExit.successor()));
2366                 } else {
2367                     // @@@ Composition of lowerable ops
2368                     if (op2 instanceof Lowerable lop2) {
2369                         block2 = lop2.lower(block2, opT);
2370                     } else {
2371                         block2.op(op2);
2372                     }
2373                 }
2374                 return block2;
2375             }));
2376 
2377             return finallyExit;
2378         }
2379 
2380         @Override
2381         public TypeElement resultType() {
2382             return VOID;
2383         }
2384     }
2385 
2386     //
2387     // Patterns
2388 
2389     static final String Pattern_CLASS_NAME = ExtendedOp_CLASS_NAME + "$" + Pattern.class.getSimpleName();
2390 
2391     // Reified pattern nodes
2392 
2393     /**
2394      * Synthetic pattern types
2395      * // @@@ Replace with types extending from TypeElement
2396      */
2397     public sealed interface Pattern {
2398 
2399         /**
2400          * Synthetic binding pattern type.
2401          *
2402          * @param <T> the type of values that are bound
2403          */
2404         final class Binding<T> implements Pattern {
2405             Binding() {
2406             }
2407         }
2408 
2409         /**
2410          * Synthetic record pattern type.
2411          *
2412          * @param <T> the type of records that are bound
2413          */
2414         final class Record<T> implements Pattern {
2415             Record() {
2416             }
2417         }
2418 
2419         // @@@ Pattern types
2420 
2421         JavaType PATTERN_BINDING_TYPE = JavaType.type(ClassDesc.of(Pattern_CLASS_NAME +
2422                 "$" + Binding.class.getSimpleName()));
2423         JavaType PATTERN_RECORD_TYPE = JavaType.type(ClassDesc.of(Pattern_CLASS_NAME +
2424                 "$" + Pattern.Record.class.getSimpleName()));
2425 
2426         static JavaType bindingType(TypeElement t) {
2427             return parameterized(PATTERN_BINDING_TYPE, (JavaType) t);
2428         }
2429 
2430         static JavaType recordType(TypeElement t) {
2431             return parameterized(PATTERN_RECORD_TYPE, (JavaType) t);
2432         }
2433 
2434         static TypeElement targetType(TypeElement t) {
2435             return ((ClassType) t).typeArguments().get(0);
2436         }
2437     }
2438 
2439     /**
2440      * Pattern operations.
2441      */
2442     public static final class PatternOps {
2443         PatternOps() {
2444         }
2445 
2446         /**
2447          * The pattern operation.
2448          */
2449         public sealed static abstract class PatternOp extends ExtendedOp implements Op.Pure {
2450             PatternOp(ExternalizedOp def) {
2451                 super(def);
2452             }
2453 
2454             PatternOp(PatternOp that, CopyContext cc) {
2455                 super(that, cc);
2456             }
2457 
2458             PatternOp(String name, List<Value> operands) {
2459                 super(name, operands);
2460             }
2461         }
2462 
2463         /**
2464          * The binding pattern operation, that can model Java language type patterns.
2465          */
2466         @OpFactory.OpDeclaration(BindingPatternOp.NAME)
2467         public static final class BindingPatternOp extends PatternOp {
2468             public static final String NAME = "pattern.binding";
2469 
2470             public static final String ATTRIBUTE_BINDING_NAME = NAME + ".binding.name";
2471 
2472             final TypeElement resultType;
2473             final String bindingName;
2474 
2475             public static BindingPatternOp create(ExternalizedOp def) {
2476                 String name = def.extractAttributeValue(ATTRIBUTE_BINDING_NAME, true,
2477                         v -> switch (v) {
2478                             case String s -> s;
2479                             default ->
2480                                     throw new UnsupportedOperationException("Unsupported pattern binding name value:" + v);
2481                         });
2482                 return new BindingPatternOp(def, name);
2483             }
2484 
2485             BindingPatternOp(ExternalizedOp def, String bindingName) {
2486                 super(def);
2487 
2488                 this.bindingName = bindingName;
2489                 this.resultType = def.resultType();
2490             }
2491 
2492             BindingPatternOp(BindingPatternOp that, CopyContext cc) {
2493                 super(that, cc);
2494 
2495                 this.bindingName = that.bindingName;
2496                 this.resultType = that.resultType;
2497             }
2498 
2499             @Override
2500             public BindingPatternOp transform(CopyContext cc, OpTransformer ot) {
2501                 return new BindingPatternOp(this, cc);
2502             }
2503 
2504             BindingPatternOp(TypeElement targetType, String bindingName) {
2505                 super(NAME, List.of());
2506 
2507                 this.bindingName = bindingName;
2508                 this.resultType = Pattern.bindingType(targetType);
2509             }
2510 
2511             @Override
2512             public Map<String, Object> attributes() {
2513                 HashMap<String, Object> attrs = new HashMap<>(super.attributes());
2514                 attrs.put("", bindingName);
2515                 return attrs;
2516             }
2517 
2518             public String bindingName() {
2519                 return bindingName;
2520             }
2521 
2522             public TypeElement targetType() {
2523                 return Pattern.targetType(resultType());
2524             }
2525 
2526             @Override
2527             public TypeElement resultType() {
2528                 return resultType;
2529             }
2530         }
2531 
2532         /**
2533          * The record pattern operation, that can model Java language record patterns.
2534          */
2535         @OpFactory.OpDeclaration(RecordPatternOp.NAME)
2536         public static final class RecordPatternOp extends PatternOp {
2537             public static final String NAME = "pattern.record";
2538 
2539             public static final String ATTRIBUTE_RECORD_DESCRIPTOR = NAME + ".descriptor";
2540 
2541             final RecordTypeRef recordDescriptor;
2542 
2543             public static RecordPatternOp create(ExternalizedOp def) {
2544                 RecordTypeRef recordDescriptor = def.extractAttributeValue(ATTRIBUTE_RECORD_DESCRIPTOR, true,
2545                         v -> switch (v) {
2546                             case String s -> RecordTypeRef.ofString(s);
2547                             case RecordTypeRef rtd -> rtd;
2548                             default ->
2549                                     throw new UnsupportedOperationException("Unsupported record type descriptor value:" + v);
2550                         });
2551 
2552                 return new RecordPatternOp(def, recordDescriptor);
2553             }
2554 
2555             RecordPatternOp(ExternalizedOp def, RecordTypeRef recordDescriptor) {
2556                 super(def);
2557 
2558                 this.recordDescriptor = recordDescriptor;
2559             }
2560 
2561             RecordPatternOp(RecordPatternOp that, CopyContext cc) {
2562                 super(that, cc);
2563 
2564                 this.recordDescriptor = that.recordDescriptor;
2565             }
2566 
2567             @Override
2568             public RecordPatternOp transform(CopyContext cc, OpTransformer ot) {
2569                 return new RecordPatternOp(this, cc);
2570             }
2571 
2572             RecordPatternOp(RecordTypeRef recordDescriptor, List<Value> nestedPatterns) {
2573                 // The type of each value is a subtype of Pattern
2574                 // The number of values corresponds to the number of components of the record
2575                 super(NAME, List.copyOf(nestedPatterns));
2576 
2577                 this.recordDescriptor = recordDescriptor;
2578             }
2579 
2580             @Override
2581             public Map<String, Object> attributes() {
2582                 HashMap<String, Object> m = new HashMap<>(super.attributes());
2583                 m.put("", recordDescriptor);
2584                 return Collections.unmodifiableMap(m);
2585             }
2586 
2587             public RecordTypeRef recordDescriptor() {
2588                 return recordDescriptor;
2589             }
2590 
2591             public TypeElement targetType() {
2592                 return Pattern.targetType(resultType());
2593             }
2594 
2595             @Override
2596             public TypeElement resultType() {
2597                 return Pattern.recordType(recordDescriptor.recordType());
2598             }
2599         }
2600 
2601         /**
2602          * The match operation, that can model Java language pattern matching.
2603          */
2604         @OpFactory.OpDeclaration(MatchOp.NAME)
2605         public static final class MatchOp extends ExtendedOp implements Op.Isolated, Op.Lowerable {
2606             public static final String NAME = "pattern.match";
2607 
2608             final Body pattern;
2609             final Body match;
2610 
2611             public MatchOp(ExternalizedOp def) {
2612                 super(def);
2613 
2614                 this.pattern = def.bodyDefinitions().get(0).build(this);
2615                 this.match = def.bodyDefinitions().get(1).build(this);
2616             }
2617 
2618             MatchOp(MatchOp that, CopyContext cc, OpTransformer ot) {
2619                 super(that, cc);
2620 
2621                 this.pattern = that.pattern.transform(cc, ot).build(this);
2622                 this.match = that.match.transform(cc, ot).build(this);
2623             }
2624 
2625             @Override
2626             public MatchOp transform(CopyContext cc, OpTransformer ot) {
2627                 return new MatchOp(this, cc, ot);
2628             }
2629 
2630             MatchOp(Value target, Body.Builder patternC, Body.Builder matchC) {
2631                 super(NAME,
2632                         List.of(target));
2633 
2634                 this.pattern = patternC.build(this);
2635                 this.match = matchC.build(this);
2636             }
2637 
2638             @Override
2639             public List<Body> bodies() {
2640                 return List.of(pattern, match);
2641             }
2642 
2643             public Body pattern() {
2644                 return pattern;
2645             }
2646 
2647             public Body match() {
2648                 return match;
2649             }
2650 
2651             public Value target() {
2652                 return operands().get(0);
2653             }
2654 
2655             @Override
2656             public Block.Builder lower(Block.Builder b, OpTransformer opT) {
2657                 // No match block
2658                 Block.Builder endNoMatchBlock = b.block();
2659                 // Match block
2660                 Block.Builder endMatchBlock = b.block();
2661                 // End block
2662                 Block.Builder endBlock = b.block();
2663                 Block.Parameter matchResult = endBlock.parameter(resultType());
2664                 // Map match operation result
2665                 b.context().mapValue(result(), matchResult);
2666 
2667                 List<Value> patternValues = new ArrayList<>();
2668                 Op patternYieldOp = pattern.entryBlock().terminatingOp();
2669                 Op.Result rootPatternValue = (Op.Result) patternYieldOp.operands().get(0);
2670                 Block.Builder currentBlock = lower(endNoMatchBlock, b,
2671                         patternValues,
2672                         rootPatternValue.op(),
2673                         b.context().getValue(target()));
2674                 currentBlock.op(branch(endMatchBlock.successor()));
2675 
2676                 // No match block
2677                 // Pass false
2678                 endNoMatchBlock.op(branch(endBlock.successor(
2679                         endNoMatchBlock.op(constant(BOOLEAN, false)))));
2680 
2681                 // Match block
2682                 // Lower match body and pass true
2683                 endMatchBlock.transformBody(match, patternValues, opT.andThen((block, op) -> {
2684                     if (op instanceof YieldOp) {
2685                         block.op(branch(endBlock.successor(
2686                                 block.op(constant(BOOLEAN, true)))));
2687                     } else if (op instanceof Lowerable lop) {
2688                         // @@@ Composition of lowerable ops
2689                         block = lop.lower(block, opT);
2690                     } else {
2691                         block.op(op);
2692                     }
2693                     return block;
2694                 }));
2695 
2696                 return endBlock;
2697             }
2698 
2699             static Block.Builder lower(Block.Builder endNoMatchBlock, Block.Builder currentBlock,
2700                                        List<Value> bindings,
2701                                        Op pattern, Value target) {
2702                 if (pattern instanceof ExtendedOp.PatternOps.RecordPatternOp rp) {
2703                     return lowerRecordPattern(endNoMatchBlock, currentBlock, bindings, rp, target);
2704                 } else if (pattern instanceof ExtendedOp.PatternOps.BindingPatternOp bp) {
2705                     return lowerBindingPattern(endNoMatchBlock, currentBlock, bindings, bp, target);
2706                 } else {
2707                     throw new UnsupportedOperationException("Unknown pattern op: " + pattern);
2708                 }
2709             }
2710 
2711             static Block.Builder lowerRecordPattern(Block.Builder endNoMatchBlock, Block.Builder currentBlock,
2712                                                     List<Value> bindings,
2713                                                     ExtendedOp.PatternOps.RecordPatternOp rpOp, Value target) {
2714                 TypeElement targetType = rpOp.targetType();
2715 
2716                 Block.Builder nextBlock = currentBlock.block();
2717 
2718                 // Check if instance of target type
2719                 Op.Result isInstance = currentBlock.op(CoreOp.instanceOf(targetType, target));
2720                 currentBlock.op(conditionalBranch(isInstance, nextBlock.successor(), endNoMatchBlock.successor()));
2721 
2722                 currentBlock = nextBlock;
2723 
2724                 target = currentBlock.op(CoreOp.cast(targetType, target));
2725 
2726                 // Access component values of record and match on each as nested target
2727                 List<Value> dArgs = rpOp.operands();
2728                 for (int i = 0; i < dArgs.size(); i++) {
2729                     Op.Result nestedPattern = (Op.Result) dArgs.get(i);
2730                     // @@@ Handle exceptions?
2731                     Value nestedTarget = currentBlock.op(CoreOp.invoke(rpOp.recordDescriptor().methodForComponent(i), target));
2732 
2733                     currentBlock = lower(endNoMatchBlock, currentBlock, bindings, nestedPattern.op(), nestedTarget);
2734                 }
2735 
2736                 return currentBlock;
2737             }
2738 
2739             static Block.Builder lowerBindingPattern(Block.Builder endNoMatchBlock, Block.Builder currentBlock,
2740                                                      List<Value> bindings,
2741                                                      ExtendedOp.PatternOps.BindingPatternOp bpOp, Value target) {
2742                 TypeElement targetType = bpOp.targetType();
2743 
2744                 Block.Builder nextBlock = currentBlock.block();
2745 
2746                 // Check if instance of target type
2747                 currentBlock.op(conditionalBranch(currentBlock.op(CoreOp.instanceOf(targetType, target)),
2748                         nextBlock.successor(), endNoMatchBlock.successor()));
2749 
2750                 currentBlock = nextBlock;
2751 
2752                 target = currentBlock.op(CoreOp.cast(targetType, target));
2753                 bindings.add(target);
2754 
2755                 return currentBlock;
2756             }
2757 
2758             @Override
2759             public TypeElement resultType() {
2760                 return BOOLEAN;
2761             }
2762         }
2763     }
2764 
2765 
2766     /**
2767      * A factory for extended and core operations.
2768      */
2769     // @@@ Compute lazily
2770     public static final OpFactory FACTORY = CoreOp.FACTORY.andThen(OpFactory.OP_FACTORY.get(ExtendedOp.class));
2771 
2772 
2773     /**
2774      * Creates a continue operation.
2775      *
2776      * @return the continue operation
2777      */
2778     public static JavaContinueOp _continue() {
2779         return _continue(null);
2780     }
2781 
2782     /**
2783      * Creates a continue operation.
2784      *
2785      * @param label the value associated with where to continue from
2786      * @return the continue operation
2787      */
2788     public static JavaContinueOp _continue(Value label) {
2789         return new JavaContinueOp(label);
2790     }
2791 
2792     /**
2793      * Creates a break operation.
2794      *
2795      * @return the break operation
2796      */
2797     public static JavaBreakOp _break() {
2798         return _break(null);
2799     }
2800 
2801     /**
2802      * Creates a break operation.
2803      *
2804      * @param label the value associated with where to continue from
2805      * @return the break operation
2806      */
2807     public static JavaBreakOp _break(Value label) {
2808         return new JavaBreakOp(label);
2809     }
2810 
2811     /**
2812      * Creates a yield operation.
2813      *
2814      * @return the yield operation
2815      */
2816     public static JavaYieldOp java_yield() {
2817         return new JavaYieldOp();
2818     }
2819 
2820     /**
2821      * Creates a yield operation.
2822      *
2823      * @param operand the value to yield
2824      * @return the yield operation
2825      */
2826     public static JavaYieldOp java_yield(Value operand) {
2827         return new JavaYieldOp(operand);
2828     }
2829 
2830     /**
2831      * Creates a block operation.
2832      *
2833      * @param body the body builder of the operation to be built and become its child
2834      * @return the block operation
2835      */
2836     public static JavaBlockOp block(Body.Builder body) {
2837         return new JavaBlockOp(body);
2838     }
2839 
2840     /**
2841      * Creates a labeled operation.
2842      *
2843      * @param body the body builder of the operation to be built and become its child
2844      * @return the block operation
2845      */
2846     public static JavaLabeledOp labeled(Body.Builder body) {
2847         return new JavaLabeledOp(body);
2848     }
2849 
2850     /**
2851      * Creates an if operation builder.
2852      *
2853      * @param ancestorBody the nearest ancestor body builder from which to construct
2854      *                     body builders for this operation
2855      * @return the if operation builder
2856      */
2857     public static JavaIfOp.IfBuilder _if(Body.Builder ancestorBody) {
2858         return new JavaIfOp.IfBuilder(ancestorBody);
2859     }
2860 
2861     // Pairs of
2862     //   predicate ()boolean, body ()void
2863     // And one optional body ()void at the end
2864 
2865     /**
2866      * Creates an if operation.
2867      *
2868      * @param bodies the body builders of operation to be built and become its children
2869      * @return the if operation
2870      */
2871     public static JavaIfOp _if(List<Body.Builder> bodies) {
2872         return new JavaIfOp(bodies);
2873     }
2874 
2875     /**
2876      * Creates a switch expression operation.
2877      * <p>
2878      * The result type of the operation will be derived from the yield type of the second body
2879      *
2880      * @param target the switch target value
2881      * @param bodies the body builders of the operation to be built and become its children
2882      * @return the switch expression operation
2883      */
2884     public static JavaSwitchExpressionOp switchExpression(Value target, List<Body.Builder> bodies) {
2885         return new JavaSwitchExpressionOp(null, target, bodies);
2886     }
2887 
2888     /**
2889      * Creates a switch expression operation.
2890      *
2891      * @param resultType the result type of the expression
2892      * @param target     the switch target value
2893      * @param bodies     the body builders of the operation to be built and become its children
2894      * @return the switch expression operation
2895      */
2896     public static JavaSwitchExpressionOp switchExpression(TypeElement resultType, Value target,
2897                                                           List<Body.Builder> bodies) {
2898         Objects.requireNonNull(resultType);
2899         return new JavaSwitchExpressionOp(resultType, target, bodies);
2900     }
2901 
2902     /**
2903      * Creates a switch fallthrough operation.
2904      *
2905      * @return the switch fallthrough operation
2906      */
2907     public static JavaSwitchFallthroughOp switchFallthroughOp() {
2908         return new JavaSwitchFallthroughOp();
2909     }
2910 
2911     /**
2912      * Creates a for operation builder.
2913      *
2914      * @param ancestorBody the nearest ancestor body builder from which to construct
2915      *                     body builders for this operation
2916      * @param initTypes    the types of initialized variables
2917      * @return the for operation builder
2918      */
2919     public static JavaForOp.InitBuilder _for(Body.Builder ancestorBody, TypeElement... initTypes) {
2920         return _for(ancestorBody, List.of(initTypes));
2921     }
2922 
2923     /**
2924      * Creates a for operation builder.
2925      *
2926      * @param ancestorBody the nearest ancestor body builder from which to construct
2927      *                     body builders for this operation
2928      * @param initTypes    the types of initialized variables
2929      * @return the for operation builder
2930      */
2931     public static JavaForOp.InitBuilder _for(Body.Builder ancestorBody, List<? extends TypeElement> initTypes) {
2932         return new JavaForOp.InitBuilder(ancestorBody, initTypes);
2933     }
2934 
2935 
2936     /**
2937      * Creates a for operation.
2938      *
2939      * @param init   the init body builder of the operation to be built and become its child
2940      * @param cond   the cond body builder of the operation to be built and become its child
2941      * @param update the update body builder of the operation to be built and become its child
2942      * @param body   the main body builder of the operation to be built and become its child
2943      * @return the for operation
2944      */
2945     // init ()Tuple<Var<T1>, Var<T2>, ..., Var<TN>>, or init ()void
2946     // cond (Var<T1>, Var<T2>, ..., Var<TN>)boolean
2947     // update (Var<T1>, Var<T2>, ..., Var<TN>)void
2948     // body (Var<T1>, Var<T2>, ..., Var<TN>)void
2949     public static JavaForOp _for(Body.Builder init,
2950                                  Body.Builder cond,
2951                                  Body.Builder update,
2952                                  Body.Builder body) {
2953         return new JavaForOp(init, cond, update, body);
2954     }
2955 
2956     /**
2957      * Creates an enhanced for operation builder.
2958      *
2959      * @param ancestorBody the nearest ancestor body builder from which to construct
2960      *                     body builders for this operation
2961      * @param iterableType the iterable type
2962      * @param elementType  the element type
2963      * @return the enhanced for operation builder
2964      */
2965     public static JavaEnhancedForOp.ExpressionBuilder enhancedFor(Body.Builder ancestorBody,
2966                                                                   TypeElement iterableType, TypeElement elementType) {
2967         return new JavaEnhancedForOp.ExpressionBuilder(ancestorBody, iterableType, elementType);
2968     }
2969 
2970     // expression ()I<E>
2971     // init (E )Var<T>
2972     // body (Var<T> )void
2973 
2974     /**
2975      * Creates an enhanced for operation.
2976      *
2977      * @param expression the expression body builder of the operation to be built and become its child
2978      * @param init       the init body builder of the operation to be built and become its child
2979      * @param body       the main body builder of the operation to be built and become its child
2980      * @return the enhanced for operation
2981      */
2982     public static JavaEnhancedForOp enhancedFor(Body.Builder expression,
2983                                                 Body.Builder init,
2984                                                 Body.Builder body) {
2985         return new JavaEnhancedForOp(expression, init, body);
2986     }
2987 
2988     /**
2989      * Creates a while operation builder.
2990      *
2991      * @param ancestorBody the nearest ancestor body builder from which to construct
2992      *                     body builders for this operation
2993      * @return the while operation builder
2994      */
2995     public static JavaWhileOp.PredicateBuilder _while(Body.Builder ancestorBody) {
2996         return new JavaWhileOp.PredicateBuilder(ancestorBody);
2997     }
2998 
2999     /**
3000      * Creates a while operation.
3001      *
3002      * @param predicate the predicate body builder of the operation to be built and become its child
3003      * @param body      the main body builder of the operation to be built and become its child
3004      * @return the while operation
3005      */
3006     // predicate, ()boolean, may be null for predicate returning true
3007     // body, ()void
3008     public static JavaWhileOp _while(Body.Builder predicate, Body.Builder body) {
3009         return new JavaWhileOp(predicate, body);
3010     }
3011 
3012     /**
3013      * Creates a do operation builder.
3014      *
3015      * @param ancestorBody the nearest ancestor body builder from which to construct
3016      *                     body builders for this operation
3017      * @return the do operation builder
3018      */
3019     public static JavaDoWhileOp.BodyBuilder doWhile(Body.Builder ancestorBody) {
3020         return new JavaDoWhileOp.BodyBuilder(ancestorBody);
3021     }
3022 
3023     /**
3024      * Creates a do operation.
3025      *
3026      * @param predicate the predicate body builder of the operation to be built and become its child
3027      * @param body      the main body builder of the operation to be built and become its child
3028      * @return the do operation
3029      */
3030     public static JavaDoWhileOp doWhile(Body.Builder body, Body.Builder predicate) {
3031         return new JavaDoWhileOp(body, predicate);
3032     }
3033 
3034     /**
3035      * Creates a conditional-and operation builder.
3036      *
3037      * @param ancestorBody the nearest ancestor body builder from which to construct
3038      *                     body builders for this operation
3039      * @param lhs          a consumer that builds the left-hand side body
3040      * @param rhs          a consumer that builds the right-hand side body
3041      * @return the conditional-and operation builder
3042      */
3043     public static JavaConditionalAndOp.Builder conditionalAnd(Body.Builder ancestorBody,
3044                                                               Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
3045         return new JavaConditionalAndOp.Builder(ancestorBody, lhs, rhs);
3046     }
3047 
3048     /**
3049      * Creates a conditional-or operation builder.
3050      *
3051      * @param ancestorBody the nearest ancestor body builder from which to construct
3052      *                     body builders for this operation
3053      * @param lhs          a consumer that builds the left-hand side body
3054      * @param rhs          a consumer that builds the right-hand side body
3055      * @return the conditional-or operation builder
3056      */
3057     public static JavaConditionalOrOp.Builder conditionalOr(Body.Builder ancestorBody,
3058                                                             Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
3059         return new JavaConditionalOrOp.Builder(ancestorBody, lhs, rhs);
3060     }
3061 
3062     /**
3063      * Creates a conditional-and operation
3064      *
3065      * @param bodies the body builders of operation to be built and become its children
3066      * @return the conditional-and operation
3067      */
3068     // predicates, ()boolean
3069     public static JavaConditionalAndOp conditionalAnd(List<Body.Builder> bodies) {
3070         return new JavaConditionalAndOp(bodies);
3071     }
3072 
3073     /**
3074      * Creates a conditional-or operation
3075      *
3076      * @param bodies the body builders of operation to be built and become its children
3077      * @return the conditional-or operation
3078      */
3079     // predicates, ()boolean
3080     public static JavaConditionalOrOp conditionalOr(List<Body.Builder> bodies) {
3081         return new JavaConditionalOrOp(bodies);
3082     }
3083 
3084     /**
3085      * Creates a conditional operation
3086      *
3087      * @param expressionType the result type of the expression
3088      * @param bodies         the body builders of operation to be built and become its children
3089      * @return the conditional operation
3090      */
3091     public static JavaConditionalExpressionOp conditionalExpression(TypeElement expressionType,
3092                                                                     List<Body.Builder> bodies) {
3093         Objects.requireNonNull(expressionType);
3094         return new JavaConditionalExpressionOp(expressionType, bodies);
3095     }
3096 
3097     /**
3098      * Creates a conditional operation
3099      * <p>
3100      * The result type of the operation will be derived from the yield type of the second body
3101      *
3102      * @param bodies the body builders of operation to be built and become its children
3103      * @return the conditional operation
3104      */
3105     public static JavaConditionalExpressionOp conditionalExpression(List<Body.Builder> bodies) {
3106         return new JavaConditionalExpressionOp(null, bodies);
3107     }
3108 
3109     /**
3110      * Creates try operation builder.
3111      *
3112      * @param ancestorBody the nearest ancestor body builder from which to construct
3113      *                     body builders for this operation
3114      * @param c            a consumer that builds the try body
3115      * @return the try operation builder
3116      */
3117     public static JavaTryOp.CatchBuilder _try(Body.Builder ancestorBody, Consumer<Block.Builder> c) {
3118         Body.Builder _try = Body.Builder.of(ancestorBody, FunctionType.VOID);
3119         c.accept(_try.entryBlock());
3120         return new JavaTryOp.CatchBuilder(ancestorBody, null, _try);
3121     }
3122 
3123     /**
3124      * Creates try-with-resources operation builder.
3125      *
3126      * @param ancestorBody the nearest ancestor body builder from which to construct
3127      *                     body builders for this operation
3128      * @param c            a consumer that builds the resources body
3129      * @return the try-with-resources operation builder
3130      */
3131     public static JavaTryOp.BodyBuilder tryWithResources(Body.Builder ancestorBody,
3132                                                          List<? extends TypeElement> resourceTypes,
3133                                                          Consumer<Block.Builder> c) {
3134         resourceTypes = resourceTypes.stream().map(VarType::varType).toList();
3135         Body.Builder resources = Body.Builder.of(ancestorBody,
3136                 FunctionType.functionType(TupleType.tupleType(resourceTypes)));
3137         c.accept(resources.entryBlock());
3138         return new JavaTryOp.BodyBuilder(ancestorBody, resourceTypes, resources);
3139     }
3140 
3141     // resources ()Tuple<Var<R1>, Var<R2>, ..., Var<RN>>, or null
3142     // try (Var<R1>, Var<R2>, ..., Var<RN>)void, or try ()void
3143     // catch (E )void, where E <: Throwable
3144     // finally ()void, or null
3145 
3146     /**
3147      * Creates a try or try-with-resources operation.
3148      *
3149      * @param resources the try body builder of the operation to be built and become its child,
3150      *                  may be null
3151      * @param body      the try body builder of the operation to be built and become its child
3152      * @param catchers  the catch body builders of the operation to be built and become its children
3153      * @param finalizer the finalizer body builder of the operation to be built and become its child
3154      * @return the try or try-with-resources operation
3155      */
3156     public static JavaTryOp _try(Body.Builder resources,
3157                                  Body.Builder body,
3158                                  List<Body.Builder> catchers,
3159                                  Body.Builder finalizer) {
3160         return new JavaTryOp(resources, body, catchers, finalizer);
3161     }
3162 
3163     //
3164     // Patterns
3165 
3166     /**
3167      * Creates a pattern match operation.
3168      *
3169      * @param target  the target value
3170      * @param pattern the pattern body builder of the operation to be built and become its child
3171      * @param match   the match body builder of the operation to be built and become its child
3172      * @return the pattern match operation
3173      */
3174     public static PatternOps.MatchOp match(Value target,
3175                                            Body.Builder pattern, Body.Builder match) {
3176         return new PatternOps.MatchOp(target, pattern, match);
3177     }
3178 
3179     /**
3180      * Creates a pattern binding operation.
3181      *
3182      * @param type        the type of value to be bound
3183      * @param bindingName the binding name
3184      * @return the pattern binding operation
3185      */
3186     public static PatternOps.BindingPatternOp bindingPattern(TypeElement type, String bindingName) {
3187         return new PatternOps.BindingPatternOp(type, bindingName);
3188     }
3189 
3190     /**
3191      * Creates a record pattern operation.
3192      *
3193      * @param recordDescriptor the record descriptor
3194      * @param nestedPatterns   the nested pattern values
3195      * @return the record pattern operation
3196      */
3197     public static PatternOps.RecordPatternOp recordPattern(RecordTypeRef recordDescriptor, Value... nestedPatterns) {
3198         return recordPattern(recordDescriptor, List.of(nestedPatterns));
3199     }
3200 
3201     /**
3202      * Creates a record pattern operation.
3203      *
3204      * @param recordDescriptor the record descriptor
3205      * @param nestedPatterns   the nested pattern values
3206      * @return the record pattern operation
3207      */
3208     public static PatternOps.RecordPatternOp recordPattern(RecordTypeRef recordDescriptor, List<Value> nestedPatterns) {
3209         return new PatternOps.RecordPatternOp(recordDescriptor, nestedPatterns);
3210     }
3211 
3212 }