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