1 /*
   2  * Copyright (c) 2024, 2025, 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.dialect.java;
  27 
  28 import java.lang.constant.ClassDesc;
  29 import jdk.incubator.code.*;
  30 import jdk.incubator.code.extern.DialectFactory;
  31 import jdk.incubator.code.dialect.core.*;
  32 import jdk.incubator.code.extern.ExternalizedOp;
  33 import jdk.incubator.code.extern.OpFactory;
  34 import jdk.incubator.code.internal.BranchTarget;
  35 import jdk.incubator.code.internal.OpDeclaration;
  36 
  37 import java.util.*;
  38 import java.util.concurrent.atomic.AtomicBoolean;
  39 import java.util.function.BiFunction;
  40 import java.util.function.Consumer;
  41 import java.util.function.Function;
  42 import java.util.function.Predicate;
  43 import java.util.stream.Stream;
  44 
  45 import static jdk.incubator.code.Op.Lowerable.*;
  46 import static jdk.incubator.code.CodeTransformer.*;
  47 import static jdk.incubator.code.dialect.core.CoreOp.*;
  48 import static jdk.incubator.code.dialect.java.JavaType.*;
  49 
  50 /**
  51  * The top-level operation class for Java operations.
  52  * <p>
  53  * A code model, produced by the Java compiler from Java program source, may consist of core operations and Java
  54  * operations. Such a model represents the same Java program and preserves the program meaning as defined by the
  55  * Java Language Specification
  56  * <p>
  57  * Java operations model specific Java language constructs or Java program behaviour. Some Java operations model
  58  * structured control flow and nested code. These operations are transformable, commonly referred to as lowering, into
  59  * a sequence of other core or Java operations. Those that implement {@link Op.Lowerable} can transform themselves and
  60  * will transform associated operations that are not explicitly lowerable.
  61  * <p>
  62  * A code model, produced by the Java compiler from source, and consisting of core operations and Java operations
  63  * can be transformed to one consisting only of non-lowerable operations, where all lowerable operations are lowered.
  64  * This transformation preserves programing meaning. The resulting lowered code model also represents the same Java
  65  * program.
  66  */
  67 public sealed abstract class JavaOp extends Op {
  68 
  69     JavaOp(Op that, CodeContext cc) {
  70         super(that, cc);
  71     }
  72 
  73     JavaOp(List<? extends Value> operands) {
  74         super(operands);
  75     }
  76 
  77     @Override
  78     public String externalizeOpName() {
  79         OpDeclaration opDecl = this.getClass().getDeclaredAnnotation(OpDeclaration.class);
  80         assert opDecl != null : this.getClass().getName();
  81         return opDecl.value();
  82     }
  83 
  84     /**
  85      * An operation that models a Java expression
  86      */
  87     public sealed interface JavaExpression permits
  88             ArithmeticOperation,
  89             ArrayAccessOp.ArrayLoadOp,
  90             ArrayAccessOp.ArrayStoreOp,
  91             ArrayLengthOp,
  92             CastOp,
  93             ConvOp,
  94             ConcatOp,
  95             ConstantOp,
  96             FieldAccessOp.FieldLoadOp,
  97             FieldAccessOp.FieldStoreOp,
  98             InstanceOfOp,
  99             InvokeOp,
 100             LambdaOp,
 101             NewOp,
 102             TestOperation,
 103             VarAccessOp.VarLoadOp,
 104             VarAccessOp.VarStoreOp,
 105             ConditionalExpressionOp,
 106             JavaConditionalOp,
 107             SwitchExpressionOp {
 108     }
 109 
 110     /**
 111      * An operation that models a Java statement
 112      */
 113     public sealed interface JavaStatement permits
 114             ArrayAccessOp.ArrayStoreOp,
 115             AssertOp,
 116             FieldAccessOp.FieldStoreOp,
 117             InvokeOp,
 118             NewOp,
 119             ReturnOp,
 120             ThrowOp,
 121             VarAccessOp.VarStoreOp,
 122             VarOp,
 123             BlockOp,
 124             DoWhileOp,
 125             EnhancedForOp,
 126             ForOp,
 127             IfOp,
 128             JavaLabelOp,
 129             LabeledOp,
 130             SynchronizedOp,
 131             TryOp,
 132             WhileOp,
 133             YieldOp,
 134             SwitchStatementOp {
 135     }
 136 
 137     /**
 138      * An operation characteristic indicating the operation's behavior may be emulated using Java reflection.
 139      * A descriptor is derived from or declared by the operation that can be resolved at runtime to
 140      * an instance of a reflective handle or member. That handle or member can be operated on to
 141      * emulate the operation's behavior, specifically as bytecode behavior.
 142      */
 143     public sealed interface ReflectiveOp {
 144     }
 145 
 146     /**
 147      * An operation that performs access.
 148      */
 149     public sealed interface AccessOp permits
 150         CoreOp.VarAccessOp,
 151         FieldAccessOp,
 152         ArrayAccessOp {
 153     }
 154 
 155 
 156 
 157     /**
 158      * The lambda operation, that can model a Java lambda expression.
 159      */
 160     @OpDeclaration(LambdaOp.NAME)
 161     public static final class LambdaOp extends JavaOp
 162             implements Invokable, Lowerable, JavaExpression {
 163 
 164         /**
 165          * A builder for constructing a lambda operation.
 166          */
 167         public static class Builder {
 168             final Body.Builder ancestorBody;
 169             final FunctionType funcType;
 170             final TypeElement functionalInterface;
 171             final boolean isReflectable;
 172 
 173             Builder(Body.Builder ancestorBody, FunctionType funcType, TypeElement functionalInterface) {
 174                 this.ancestorBody = ancestorBody;
 175                 this.funcType = funcType;
 176                 this.functionalInterface = functionalInterface;
 177                 this.isReflectable = false;
 178             }
 179 
 180             Builder(Body.Builder ancestorBody, FunctionType funcType, TypeElement functionalInterface,
 181                     boolean isReflectable) {
 182                 this.ancestorBody = ancestorBody;
 183                 this.funcType = funcType;
 184                 this.functionalInterface = functionalInterface;
 185                 this.isReflectable = isReflectable;
 186             }
 187 
 188             /**
 189              * Builds a lambda operation with the provided function body.
 190              *
 191              * @param c a consumer to populate the lambda function body
 192              * @return the completed lambda operation
 193              */
 194             public LambdaOp body(Consumer<Block.Builder> c) {
 195                 Body.Builder body = Body.Builder.of(ancestorBody, funcType);
 196                 c.accept(body.entryBlock());
 197                 return new LambdaOp(functionalInterface, body, isReflectable);
 198             }
 199 
 200             /**
 201              * Marks the constructed lambda operation as reflectable.
 202              * @return this builder
 203              * @see Reflect
 204              */
 205             public Builder reflectable() {
 206                 return new Builder(ancestorBody, funcType, functionalInterface, true);
 207             }
 208         }
 209 
 210         static final String NAME = "lambda";
 211         static final String ATTRIBUTE_LAMBDA_IS_REFLECTABLE = NAME + ".isReflectable";
 212 
 213         final TypeElement functionalInterface;
 214         final Body body;
 215         final boolean isReflectable;
 216 
 217         LambdaOp(ExternalizedOp def) {
 218             boolean isReflectable = def.extractAttributeValue(ATTRIBUTE_LAMBDA_IS_REFLECTABLE,
 219                     false, v -> switch (v) {
 220                         case Boolean b -> b;
 221                         case null, default -> false;
 222                     });
 223 
 224             this(def.resultType(), def.bodyDefinitions().get(0), isReflectable);
 225         }
 226 
 227         LambdaOp(LambdaOp that, CodeContext cc, CodeTransformer ot) {
 228             super(that, cc);
 229 
 230             this.functionalInterface = that.functionalInterface;
 231             this.body = that.body.transform(cc, ot).build(this);
 232             this.isReflectable = that.isReflectable;
 233         }
 234 
 235         @Override
 236         public LambdaOp transform(CodeContext cc, CodeTransformer ot) {
 237             return new LambdaOp(this, cc, ot);
 238         }
 239 
 240         LambdaOp(TypeElement functionalInterface, Body.Builder bodyC, boolean isReflectable) {
 241             super(List.of());
 242 
 243             this.functionalInterface = functionalInterface;
 244             this.body = bodyC.build(this);
 245             this.isReflectable = isReflectable;
 246         }
 247 
 248         @Override
 249         public List<Body> bodies() {
 250             return List.of(body);
 251         }
 252 
 253         @Override
 254         public FunctionType invokableType() {
 255             return body.bodyType();
 256         }
 257 
 258         /**
 259          * {@return the functional interface type modeled by this lambda operation}
 260          */
 261         public TypeElement functionalInterface() {
 262             return functionalInterface;
 263         }
 264 
 265         @Override
 266         public Body body() {
 267             return body;
 268         }
 269 
 270         @Override
 271         public Block.Builder lower(Block.Builder b, CodeTransformer _ignore) {
 272             // Isolate body with respect to ancestor transformations
 273             b.rebind(b.context(), CodeTransformer.LOWERING_TRANSFORMER).op(this);
 274             return b;
 275         }
 276 
 277         @Override
 278         public TypeElement resultType() {
 279             return functionalInterface();
 280         }
 281 
 282         /**
 283          * {@return whether this lambda operation is reflectable}
 284          * @see Reflect
 285          */
 286         public boolean isReflectable() {
 287             return isReflectable;
 288         }
 289 
 290         @Override
 291         public Map<String, Object> externalize() {
 292             return Map.of(ATTRIBUTE_LAMBDA_IS_REFLECTABLE, isReflectable);
 293         }
 294 
 295         /**
 296          * Determines if this lambda operation could have originated from a
 297          * method reference declared in Java source code.
 298          * <p>
 299          * Such a lambda operation is one with the following constraints:
 300          * <ol>
 301          *     <li>Zero or one captured value (assuming correspondence to the {@code this} variable).
 302          *     <li>A body with only one (entry) block that contains only variable declaration
 303          *     operations, variable load operations, invoke operations to box or unbox
 304          *     primitive values, a single invoke operation to the method that is
 305          *     referenced, and a return operation.
 306          *     <li>if the return operation returns a non-void result then that result is,
 307          *     or uniquely depends on, the result of the referencing invoke operation.
 308          *     <li>If the lambda operation captures one value then the first operand corresponds
 309          *     to captured the value, and subsequent operands of the referencing invocation
 310          *     operation are, or uniquely depend on, the lambda operation's parameters, in order.
 311          *     Otherwise, the first and subsequent operands of the referencing invocation
 312          *     operation are, or uniquely depend on, the lambda operation's parameters, in order.
 313          * </ol>
 314          * A value, V2, uniquely depends on another value, V1, if the graph of what V2 depends on
 315          * contains only nodes with single edges terminating in V1, and the graph of what depends on V1
 316          * is bidirectionally equal to the graph of what V2 depends on.
 317          *
 318          * @return the invocation operation to the method referenced by the lambda
 319          * operation, otherwise empty.
 320          */
 321         public Optional<InvokeOp> methodReference() {
 322             // Single block
 323             if (body().blocks().size() > 1) {
 324                 return Optional.empty();
 325             }
 326 
 327             // Zero or one (this) capture
 328             List<Value> cvs = capturedValues();
 329             if (cvs.size() > 1) {
 330                 return Optional.empty();
 331             }
 332 
 333             Map<Value, Value> valueMapping = new HashMap<>();
 334             InvokeOp methodRefInvokeOp = extractMethodInvoke(valueMapping, body().entryBlock().ops());
 335             if (methodRefInvokeOp == null) {
 336                 return Optional.empty();
 337             }
 338 
 339             // Lambda's parameters map in encounter order with the invocation's operands
 340             List<Value> lambdaParameters = new ArrayList<>();
 341             if (cvs.size() == 1) {
 342                 lambdaParameters.add(cvs.getFirst());
 343             }
 344             lambdaParameters.addAll(parameters());
 345             List<Value> methodRefOperands = methodRefInvokeOp.operands().stream().map(valueMapping::get).toList();
 346             if (!lambdaParameters.equals(methodRefOperands)) {
 347                 return Optional.empty();
 348             }
 349 
 350             return Optional.of(methodRefInvokeOp);
 351         }
 352 
 353         /**
 354          * Determines if this lambda operation contains a direct invocation of a method.
 355          * <p>
 356          * Such a lambda operation is one with the following constraints:
 357          * <ol>
 358          *     <li>A body with only one (entry) block that contains only variable declaration
 359          *     operations, variable load operations, invoke operations to box or unbox
 360          *     primitive values, a single invoke operation to the method that is
 361          *     referenced, and a return operation.
 362          *     <li>if the return operation returns a non-void result then that result is,
 363          *     or uniquely depends on, the result of the referencing invoke operation.
 364          * </ol>
 365          * A value, V2, uniquely depends on another value, V1, if the graph of what V2 depends on
 366          * contains only nodes with single edges terminating in V1, and the graph of what depends on V1
 367          * is bidirectionally equal to the graph of what V2 depends on.
 368          *
 369          * @return the invocation operation to the method referenced by the lambda
 370          * operation, otherwise empty.
 371          */
 372         public Optional<InvokeOp> directInvocation() {
 373             // Single block
 374             if (body().blocks().size() > 1) {
 375                 return Optional.empty();
 376             }
 377 
 378             Map<Value, Value> valueMapping = new HashMap<>();
 379             InvokeOp methodRefInvokeOp = extractMethodInvoke(valueMapping, body().entryBlock().ops());
 380             if (methodRefInvokeOp == null) {
 381                 return Optional.empty();
 382             }
 383 
 384             return Optional.of(methodRefInvokeOp);
 385         }
 386 
 387         /**
 388          * Converts this lambda operation to an equivalent function operation.
 389          *
 390          * @param lambdaName the name to use for the resulting function (may be empty, or {@code null})
 391          * @return a function operation that models this lambda
 392          */
 393         public CoreOp.FuncOp toFuncOp(String lambdaName) {
 394             if (lambdaName == null) lambdaName = "";
 395             List<TypeElement> parameters = new ArrayList<>(this.invokableType().parameterTypes());
 396             for (Value v : this.capturedValues()) {
 397                 TypeElement capturedType = v.type() instanceof VarType varType ? varType.valueType() : v.type();
 398                 parameters.add(capturedType);
 399             }
 400             return CoreOp.func(lambdaName, CoreType.functionType(this.invokableType().returnType(), parameters)).body(builder -> {
 401                 int idx = this.invokableType().parameterTypes().size();
 402                 for (Value v : capturedValues()) {
 403                     Block.Parameter p = builder.parameters().get(idx++);
 404                     Value functionValue = v.type() instanceof VarType ? builder.op(CoreOp.var(p)) : p;
 405                     builder.context().mapValue(v, functionValue);
 406                 }
 407                 List<Block.Parameter> outputValues = builder.parameters().subList(0, this.invokableType().parameterTypes().size());
 408                 builder.body(this.body(), outputValues, CodeTransformer.COPYING_TRANSFORMER);
 409             });
 410         }
 411 
 412         static InvokeOp extractMethodInvoke(Map<Value, Value> valueMapping, List<Op> ops) {
 413             InvokeOp methodRefInvokeOp = null;
 414             for (Op op : ops) {
 415                 switch (op) {
 416                     case VarOp varOp -> {
 417                         if (isValueUsedWithOp(varOp.result(), o -> o instanceof VarAccessOp.VarStoreOp)) {
 418                             return null;
 419                         }
 420                     }
 421                     case VarAccessOp.VarLoadOp varLoadOp -> {
 422                         Value v = varLoadOp.varOp().operands().getFirst();
 423                         valueMapping.put(varLoadOp.result(), valueMapping.getOrDefault(v, v));
 424                     }
 425                     case InvokeOp iop when isBoxOrUnboxInvocation(iop) -> {
 426                         Value v = iop.operands().getFirst();
 427                         valueMapping.put(iop.result(), valueMapping.getOrDefault(v, v));
 428                     }
 429                     case InvokeOp iop -> {
 430                         if (methodRefInvokeOp != null) {
 431                             return null;
 432                         }
 433 
 434                         for (Value o : iop.operands()) {
 435                             valueMapping.put(o, valueMapping.getOrDefault(o, o));
 436                         }
 437                         methodRefInvokeOp = iop;
 438                     }
 439                     case ReturnOp rop -> {
 440                         if (methodRefInvokeOp == null) {
 441                             return null;
 442                         }
 443                         Value r = rop.returnValue();
 444                         if (r == null) break;
 445                         if (!(valueMapping.getOrDefault(r, r) instanceof Result invokeResult)) {
 446                             return null;
 447                         }
 448                         if (invokeResult.op() != methodRefInvokeOp) {
 449                             return null;
 450                         }
 451                         assert methodRefInvokeOp.result().uses().size() == 1;
 452                     }
 453                     default -> {
 454                         return null;
 455                     }
 456                 }
 457             }
 458 
 459             return methodRefInvokeOp;
 460         }
 461 
 462         private static boolean isValueUsedWithOp(Value value, Predicate<Op> opPredicate) {
 463             for (Result user : value.uses()) {
 464                 if (opPredicate.test(user.op())) {
 465                     return true;
 466                 }
 467             }
 468             return false;
 469         }
 470 
 471         // @@@ Move to functionality on JavaType(s)
 472         static final Set<String> UNBOX_NAMES = Set.of(
 473                 "byteValue",
 474                 "shortValue",
 475                 "charValue",
 476                 "intValue",
 477                 "longValue",
 478                 "floatValue",
 479                 "doubleValue",
 480                 "booleanValue");
 481 
 482         private static boolean isBoxOrUnboxInvocation(InvokeOp iop) {
 483             MethodRef mr = iop.invokeDescriptor();
 484             return mr.refType() instanceof ClassType ct && ct.unbox().isPresent() &&
 485                     (UNBOX_NAMES.contains(mr.name()) || mr.name().equals("valueOf"));
 486         }
 487     }
 488 
 489     /**
 490      * The terminating throw operation, that can model the Java language throw statement.
 491      */
 492     @OpDeclaration(ThrowOp.NAME)
 493     public static final class ThrowOp extends JavaOp
 494             implements BodyTerminating, JavaStatement {
 495         static final String NAME = "throw";
 496 
 497         ThrowOp(ExternalizedOp def) {
 498             if (def.operands().size() != 1) {
 499                 throw new IllegalArgumentException("Operation must have one operand " + def.name());
 500             }
 501 
 502             this(def.operands().get(0));
 503         }
 504 
 505         ThrowOp(ThrowOp that, CodeContext cc) {
 506             super(that, cc);
 507         }
 508 
 509         @Override
 510         public ThrowOp transform(CodeContext cc, CodeTransformer ot) {
 511             return new ThrowOp(this, cc);
 512         }
 513 
 514         ThrowOp(Value e) {
 515             super(List.of(e));
 516         }
 517 
 518         /**
 519          * {@return the value being thrown}
 520          */
 521         public Value argument() {
 522             return operands().get(0);
 523         }
 524 
 525         @Override
 526         public TypeElement resultType() {
 527             return VOID;
 528         }
 529     }
 530 
 531     /**
 532      * The assertion operation. Supporting assertions in statement form.
 533      */
 534     @OpDeclaration(AssertOp.NAME)
 535     public static final class AssertOp extends JavaOp
 536             implements Nested, JavaStatement {
 537         static final String NAME = "assert";
 538 
 539         private final List<Body> bodies;
 540 
 541         AssertOp(ExternalizedOp def) {
 542             this(def.bodyDefinitions());
 543         }
 544 
 545         AssertOp(List<Body.Builder> bodies) {
 546             super(List.of());
 547 
 548             if (bodies.size() != 1 && bodies.size() != 2) {
 549                 throw new IllegalArgumentException("Assert must have one or two bodies.");
 550             }
 551             this.bodies = bodies.stream().map(b -> b.build(this)).toList();
 552         }
 553 
 554         AssertOp(AssertOp that, CodeContext cc, CodeTransformer ot) {
 555             super(that, cc);
 556             this.bodies = that.bodies.stream().map(b -> b.transform(cc, ot).build(this)).toList();
 557         }
 558 
 559         @Override
 560         public Op transform(CodeContext cc, CodeTransformer ot) {
 561             return new AssertOp(this, cc, ot);
 562         }
 563 
 564         @Override
 565         public TypeElement resultType() {
 566             return VOID;
 567         }
 568 
 569         @Override
 570         public List<Body> bodies() {
 571             return this.bodies;
 572         }
 573     }
 574 
 575     /**
 576      * A monitor operation.
 577      */
 578     public sealed abstract static class MonitorOp extends JavaOp {
 579         MonitorOp(MonitorOp that, CodeContext cc) {
 580             super(that, cc);
 581         }
 582 
 583         MonitorOp(Value monitor) {
 584             super(List.of(monitor));
 585         }
 586 
 587         /**
 588          * {@return the monitor value}
 589          */
 590         public Value monitorValue() {
 591             return operands().getFirst();
 592         }
 593 
 594         @Override
 595         public TypeElement resultType() {
 596             return VOID;
 597         }
 598 
 599         /**
 600          * The monitor enter operation.
 601          */
 602         @OpDeclaration(MonitorEnterOp.NAME)
 603         public static final class MonitorEnterOp extends MonitorOp {
 604             static final String NAME = "monitor.enter";
 605 
 606             MonitorEnterOp(ExternalizedOp def) {
 607                 if (def.operands().size() != 1) {
 608                     throw new IllegalArgumentException("Operation must have one operand " + def.name());
 609                 }
 610 
 611                 this(def.operands().get(0));
 612             }
 613 
 614             MonitorEnterOp(MonitorEnterOp that, CodeContext cc) {
 615                 super(that, cc);
 616             }
 617 
 618             @Override
 619             public MonitorEnterOp transform(CodeContext cc, CodeTransformer ot) {
 620                 return new MonitorEnterOp(this, cc);
 621             }
 622 
 623             MonitorEnterOp(Value monitor) {
 624                 super(monitor);
 625             }
 626         }
 627 
 628         /**
 629          * The monitor exit operation.
 630          */
 631         @OpDeclaration(MonitorExitOp.NAME)
 632         public static final class MonitorExitOp extends MonitorOp {
 633             static final String NAME = "monitor.exit";
 634 
 635             MonitorExitOp(ExternalizedOp def) {
 636                 if (def.operands().size() != 1) {
 637                     throw new IllegalArgumentException("Operation must have one operand " + def.name());
 638                 }
 639 
 640                 this(def.operands().get(0));
 641             }
 642 
 643             MonitorExitOp(MonitorExitOp that, CodeContext cc) {
 644                 super(that, cc);
 645             }
 646 
 647             @Override
 648             public MonitorExitOp transform(CodeContext cc, CodeTransformer ot) {
 649                 return new MonitorExitOp(this, cc);
 650             }
 651 
 652             MonitorExitOp(Value monitor) {
 653                 super(monitor);
 654             }
 655         }
 656     }
 657 
 658     /**
 659      * The invoke operation, that can model Java language method invocation expressions.
 660      */
 661     @OpDeclaration(InvokeOp.NAME)
 662     public static final class InvokeOp extends JavaOp
 663             implements ReflectiveOp, JavaExpression, JavaStatement {
 664 
 665         /**
 666          * The kind of invocation.
 667          */
 668         public enum InvokeKind {
 669             /**
 670              * An invocation on a class (static) method.
 671              */
 672             STATIC,
 673             /**
 674              * An invocation on an instance method.
 675              */
 676             INSTANCE,
 677             /**
 678              * A super invocation on an instance method.
 679              */
 680             SUPER
 681         }
 682 
 683         static final String NAME = "invoke";
 684         /** The externalized attribute key for a method invocation descriptor. */
 685         public static final String ATTRIBUTE_INVOKE_DESCRIPTOR = NAME + ".descriptor";
 686         /** The externalized attribute key indicating the invocation kind. */
 687         public static final String ATTRIBUTE_INVOKE_KIND = NAME + ".kind";
 688         /** The externalized attribute key for marking a varargs invocation. */
 689         public static final String ATTRIBUTE_INVOKE_VARARGS = NAME + ".varargs";
 690 
 691         final InvokeKind invokeKind;
 692         final boolean isVarArgs;
 693         final MethodRef invokeDescriptor;
 694         final TypeElement resultType;
 695 
 696         InvokeOp(ExternalizedOp def) {
 697             // Required attribute
 698             MethodRef invokeDescriptor = def.extractAttributeValue(ATTRIBUTE_INVOKE_DESCRIPTOR,
 699                     true, v -> switch (v) {
 700                         case MethodRef md -> md;
 701                         case null, default ->
 702                                 throw new UnsupportedOperationException("Unsupported invoke descriptor value:" + v);
 703                     });
 704 
 705             // If not present defaults to false
 706             boolean isVarArgs = def.extractAttributeValue(ATTRIBUTE_INVOKE_VARARGS,
 707                     false, v -> switch (v) {
 708                         case Boolean b -> b;
 709                         case null, default -> false;
 710                     });
 711 
 712             // If not present and is not varargs defaults to class or instance invocation
 713             // based on number of operands and parameters
 714             InvokeKind ik = def.extractAttributeValue(ATTRIBUTE_INVOKE_KIND,
 715                     false, v -> switch (v) {
 716                         case String s -> InvokeKind.valueOf(s);
 717                         case InvokeKind k -> k;
 718                         case null, default -> {
 719                             if (isVarArgs) {
 720                                 // If varargs then we cannot infer invoke kind
 721                                 throw new UnsupportedOperationException("Unsupported invoke kind value:" + v);
 722                             }
 723                             int paramCount = invokeDescriptor.type().parameterTypes().size();
 724                             int argCount = def.operands().size();
 725                             yield (argCount == paramCount + 1)
 726                                     ? InvokeKind.INSTANCE
 727                                     : InvokeKind.STATIC;
 728                         }
 729                     });
 730 
 731 
 732             this(ik, isVarArgs, def.resultType(), invokeDescriptor, def.operands());
 733         }
 734 
 735         InvokeOp(InvokeOp that, CodeContext cc) {
 736             super(that, cc);
 737 
 738             this.invokeKind = that.invokeKind;
 739             this.isVarArgs = that.isVarArgs;
 740             this.invokeDescriptor = that.invokeDescriptor;
 741             this.resultType = that.resultType;
 742         }
 743 
 744         @Override
 745         public InvokeOp transform(CodeContext cc, CodeTransformer ot) {
 746             return new InvokeOp(this, cc);
 747         }
 748 
 749         InvokeOp(InvokeKind invokeKind, boolean isVarArgs, TypeElement resultType, MethodRef invokeDescriptor, List<Value> args) {
 750             super(args);
 751 
 752             validateArgCount(invokeKind, isVarArgs, invokeDescriptor, args);
 753 
 754             this.invokeKind = invokeKind;
 755             this.isVarArgs = isVarArgs;
 756             this.invokeDescriptor = invokeDescriptor;
 757             this.resultType = resultType;
 758         }
 759 
 760         static void validateArgCount(InvokeKind invokeKind, boolean isVarArgs, MethodRef invokeDescriptor, List<Value> operands) {
 761             int paramCount = invokeDescriptor.type().parameterTypes().size();
 762             int argCount = operands.size() - (invokeKind == InvokeKind.STATIC ? 0 : 1);
 763             if ((!isVarArgs && argCount != paramCount)
 764                     || argCount < paramCount - 1) {
 765                 throw new IllegalArgumentException(invokeKind + " " + isVarArgs + " " + invokeDescriptor);
 766             }
 767         }
 768 
 769         @Override
 770         public Map<String, Object> externalize() {
 771             HashMap<String, Object> m = new HashMap<>();
 772             m.put("", invokeDescriptor);
 773             if (isVarArgs) {
 774                 // If varargs then we need to declare the invoke.kind attribute
 775                 // Given a method `A::m(A... more)` and an invocation with one
 776                 // operand, we don't know if that operand corresponds to the
 777                 // receiver or a method argument
 778                 m.put(ATTRIBUTE_INVOKE_KIND, invokeKind);
 779                 m.put(ATTRIBUTE_INVOKE_VARARGS, isVarArgs);
 780             } else if (invokeKind == InvokeKind.SUPER) {
 781                 m.put(ATTRIBUTE_INVOKE_KIND, invokeKind);
 782             }
 783             return Collections.unmodifiableMap(m);
 784         }
 785 
 786         /**
 787          * {@return the invocation kind}
 788          */
 789         public InvokeKind invokeKind() {
 790             return invokeKind;
 791         }
 792 
 793         /**
 794          * {@return {@code true} if this invocation uses a variable number of arguments}
 795          */
 796         public boolean isVarArgs() {
 797             return isVarArgs;
 798         }
 799 
 800         /**
 801          * {@return the method invocation descriptor}
 802          */
 803         public MethodRef invokeDescriptor() {
 804             return invokeDescriptor;
 805         }
 806 
 807         /**
 808          * {@return {@code true} if this invocation refers to an instance method)}
 809          */
 810         public boolean hasReceiver() {
 811             return invokeKind != InvokeKind.STATIC;
 812         }
 813 
 814         /**
 815          * {@return the operands used as varargs, if this is a varargs invocation,
 816          * or {@code null}}
 817          */
 818         public List<Value> varArgOperands() {
 819             if (!isVarArgs) {
 820                 return null;
 821             }
 822 
 823             int operandCount = operands().size();
 824             int argCount = operandCount - (invokeKind == InvokeKind.STATIC ? 0 : 1);
 825             int paramCount = invokeDescriptor.type().parameterTypes().size();
 826             int varArgCount = argCount - (paramCount - 1);
 827             return operands().subList(operandCount - varArgCount, operandCount);
 828         }
 829 
 830         /**
 831          * {@return the method invocation arguments}
 832          */
 833         public List<Value> argOperands() {
 834             if (!isVarArgs) {
 835                 return operands();
 836             }
 837             int paramCount = invokeDescriptor().type().parameterTypes().size();
 838             int argOperandsCount = paramCount - (invokeKind() == InvokeKind.STATIC ? 1 : 0);
 839             return operands().subList(0, argOperandsCount);
 840         }
 841 
 842         @Override
 843         public TypeElement resultType() {
 844             return resultType;
 845         }
 846     }
 847 
 848     /**
 849      * The conversion operation, that can model Java language cast expressions
 850      * for numerical conversion, or such implicit conversion.
 851      */
 852     @OpDeclaration(ConvOp.NAME)
 853     public static final class ConvOp extends JavaOp
 854             implements Pure, JavaExpression {
 855         static final String NAME = "conv";
 856 
 857         final TypeElement resultType;
 858 
 859         ConvOp(ExternalizedOp def) {
 860             this(def.resultType(), def.operands().get(0));
 861         }
 862 
 863         ConvOp(ConvOp that, CodeContext cc) {
 864             super(that, cc);
 865 
 866             this.resultType = that.resultType;
 867         }
 868 
 869         @Override
 870         public Op transform(CodeContext cc, CodeTransformer ot) {
 871             return new ConvOp(this, cc);
 872         }
 873 
 874         ConvOp(TypeElement resultType, Value arg) {
 875             super(List.of(arg));
 876 
 877             this.resultType = resultType;
 878         }
 879 
 880         @Override
 881         public TypeElement resultType() {
 882             return resultType;
 883         }
 884     }
 885 
 886     /**
 887      * The new operation, that can models Java language instance creation expressions.
 888      */
 889     @OpDeclaration(NewOp.NAME)
 890     public static final class NewOp extends JavaOp
 891             implements ReflectiveOp, JavaExpression, JavaStatement {
 892 
 893         static final String NAME = "new";
 894         /**
 895          * The externalized attribute key for a constructor descriptor in a new operation.
 896          */
 897         public static final String ATTRIBUTE_NEW_DESCRIPTOR = NAME + ".descriptor";
 898         /**
 899          * The externalized attribute key indicating a varargs constructor in a new operation.
 900          */
 901         public static final String ATTRIBUTE_NEW_VARARGS = NAME + ".varargs";
 902 
 903         final boolean isVarArgs;
 904         final MethodRef constructorDescriptor;
 905         final TypeElement resultType;
 906 
 907         NewOp(ExternalizedOp def) {
 908             // Required attribute
 909             MethodRef constructorDescriptor = def.extractAttributeValue(ATTRIBUTE_NEW_DESCRIPTOR,
 910                     true, v -> switch (v) {
 911                         case MethodRef cd -> cd;
 912                         case null, default ->
 913                                 throw new UnsupportedOperationException("Unsupported constructor descriptor value:" + v);
 914                     });
 915 
 916             // If not present defaults to false
 917             boolean isVarArgs = def.extractAttributeValue(ATTRIBUTE_NEW_VARARGS,
 918                     false, v -> switch (v) {
 919                         case Boolean b -> b;
 920                         case null, default -> false;
 921                     });
 922 
 923             this(isVarArgs, def.resultType(), constructorDescriptor, def.operands());
 924         }
 925 
 926         NewOp(NewOp that, CodeContext cc) {
 927             super(that, cc);
 928 
 929             this.isVarArgs = that.isVarArgs;
 930             this.constructorDescriptor = that.constructorDescriptor;
 931             this.resultType = that.resultType;
 932         }
 933 
 934         @Override
 935         public NewOp transform(CodeContext cc, CodeTransformer ot) {
 936             return new NewOp(this, cc);
 937         }
 938 
 939         NewOp(boolean isVarargs, TypeElement resultType, MethodRef constructorDescriptor, List<Value> args) {
 940             super(args);
 941 
 942             validateArgCount(isVarargs, constructorDescriptor, args);
 943             if (!constructorDescriptor.isConstructor()) {
 944                 throw new IllegalArgumentException("Not a constructor descriptor: " + constructorDescriptor);
 945             }
 946 
 947             this.isVarArgs = isVarargs;
 948             this.constructorDescriptor = constructorDescriptor;
 949             this.resultType = resultType;
 950         }
 951 
 952         static void validateArgCount(boolean isVarArgs, MethodRef constructorDescriptor, List<Value> operands) {
 953             int paramCount = constructorDescriptor.type().parameterTypes().size();
 954             int argCount = operands.size();
 955             if ((!isVarArgs && argCount != paramCount)
 956                     || argCount < paramCount - 1) {
 957                 throw new IllegalArgumentException(isVarArgs + " " + constructorDescriptor);
 958             }
 959         }
 960 
 961         @Override
 962         public Map<String, Object> externalize() {
 963             HashMap<String, Object> m = new HashMap<>();
 964             m.put("", constructorDescriptor);
 965             if (isVarArgs) {
 966                 m.put(ATTRIBUTE_NEW_VARARGS, isVarArgs);
 967             }
 968             return Collections.unmodifiableMap(m);
 969         }
 970 
 971         // Indicates whether this instance creation uses a variable argument (varargs) constructor.
 972         /**
 973          * {@return {@code true}, if this instance creation operation is a varargs constructor call}
 974          */
 975         public boolean isVarargs() {
 976             return isVarArgs;
 977         }
 978 
 979         // Retrieves the resulting type produced by this instance creation operation.
 980         /**
 981          * {@return the resulting type of this instance creation operation}
 982          */
 983         public TypeElement type() {
 984             return opType().returnType();
 985         } // @@@ duplication, same as resultType()
 986 
 987         /**
 988          * {@return the constructor descriptor for this instance creation operation}
 989          */
 990         public MethodRef constructorDescriptor() {
 991             return constructorDescriptor;
 992         }
 993 
 994         @Override
 995         public TypeElement resultType() {
 996             return resultType;
 997         }
 998     }
 999 
1000     /**
1001      * A field access operation, that can model Java langauge field access expressions.
1002      */
1003     public sealed abstract static class FieldAccessOp extends JavaOp
1004             implements AccessOp, ReflectiveOp {
1005         /**
1006          * The externalized attribute modelling the field descriptor.
1007          */
1008         public static final String ATTRIBUTE_FIELD_DESCRIPTOR = "field.descriptor";
1009 
1010         final FieldRef fieldDescriptor;
1011 
1012         FieldAccessOp(FieldAccessOp that, CodeContext cc) {
1013             super(that, cc);
1014 
1015             this.fieldDescriptor = that.fieldDescriptor;
1016         }
1017 
1018         FieldAccessOp(List<Value> operands,
1019                       FieldRef fieldDescriptor) {
1020             super(operands);
1021 
1022             this.fieldDescriptor = fieldDescriptor;
1023         }
1024 
1025         @Override
1026         public Map<String, Object> externalize() {
1027             return Map.of("", fieldDescriptor);
1028         }
1029 
1030         /**
1031          * {@return the descriptor of the accessed field}
1032          */
1033         public final FieldRef fieldDescriptor() {
1034             return fieldDescriptor;
1035         }
1036 
1037         /**
1038          * The field load operation, that can model Java language field access expressions combined with load access to
1039          * field instance variables.
1040          */
1041         @OpDeclaration(FieldLoadOp.NAME)
1042         public static final class FieldLoadOp extends FieldAccessOp
1043                 implements Pure, JavaExpression {
1044             static final String NAME = "field.load";
1045 
1046             final TypeElement resultType;
1047 
1048             FieldLoadOp(ExternalizedOp def) {
1049                 if (def.operands().size() > 1) {
1050                     throw new IllegalArgumentException("Operation must accept zero or one operand");
1051                 }
1052 
1053                 FieldRef fieldDescriptor = def.extractAttributeValue(ATTRIBUTE_FIELD_DESCRIPTOR, true,
1054                         v -> switch (v) {
1055                             case FieldRef fd -> fd;
1056                             case null, default ->
1057                                     throw new UnsupportedOperationException("Unsupported field descriptor value:" + v);
1058                         });
1059 
1060                 super(def.operands(), fieldDescriptor);
1061 
1062                 this.resultType = def.resultType();
1063             }
1064 
1065             FieldLoadOp(FieldLoadOp that, CodeContext cc) {
1066                 super(that, cc);
1067 
1068                 resultType = that.resultType();
1069             }
1070 
1071             @Override
1072             public FieldLoadOp transform(CodeContext cc, CodeTransformer ot) {
1073                 return new FieldLoadOp(this, cc);
1074             }
1075 
1076             // instance
1077             FieldLoadOp(TypeElement resultType, FieldRef descriptor, Value receiver) {
1078                 super(List.of(receiver), descriptor);
1079 
1080                 this.resultType = resultType;
1081             }
1082 
1083             // static
1084             FieldLoadOp(TypeElement resultType, FieldRef descriptor) {
1085                 super(List.of(), descriptor);
1086 
1087                 this.resultType = resultType;
1088             }
1089 
1090             @Override
1091             public TypeElement resultType() {
1092                 return resultType;
1093             }
1094         }
1095 
1096         /**
1097          * The field store operation, that can model Java language field access expressions combined with store access
1098          * to field instance variables.
1099          */
1100         @OpDeclaration(FieldStoreOp.NAME)
1101         public static final class FieldStoreOp extends FieldAccessOp
1102                 implements JavaExpression, JavaStatement {
1103             static final String NAME = "field.store";
1104 
1105             FieldStoreOp(ExternalizedOp def) {
1106                 if (def.operands().isEmpty() || def.operands().size() > 2) {
1107                     throw new IllegalArgumentException("Operation must accept one or two operands");
1108                 }
1109 
1110                 FieldRef fieldDescriptor = def.extractAttributeValue(ATTRIBUTE_FIELD_DESCRIPTOR, true,
1111                         v -> switch (v) {
1112                             case FieldRef fd -> fd;
1113                             case null, default ->
1114                                     throw new UnsupportedOperationException("Unsupported field descriptor value:" + v);
1115                         });
1116 
1117                 super(def.operands(), fieldDescriptor);
1118             }
1119 
1120             FieldStoreOp(FieldStoreOp that, CodeContext cc) {
1121                 super(that, cc);
1122             }
1123 
1124             @Override
1125             public FieldStoreOp transform(CodeContext cc, CodeTransformer ot) {
1126                 return new FieldStoreOp(this, cc);
1127             }
1128 
1129             // instance
1130             FieldStoreOp(FieldRef descriptor, Value receiver, Value v) {
1131                 super(List.of(receiver, v), descriptor);
1132             }
1133 
1134             // static
1135             FieldStoreOp(FieldRef descriptor, Value v) {
1136                 super(List.of(v), descriptor);
1137             }
1138 
1139             @Override
1140             public TypeElement resultType() {
1141                 return VOID;
1142             }
1143         }
1144     }
1145 
1146     /**
1147      * The array length operation, that can model Java language field access expressions to the length field of an
1148      * array.
1149      */
1150     @OpDeclaration(ArrayLengthOp.NAME)
1151     public static final class ArrayLengthOp extends JavaOp
1152             implements ReflectiveOp, JavaExpression {
1153         static final String NAME = "array.length";
1154 
1155         ArrayLengthOp(ExternalizedOp def) {
1156             this(def.operands().get(0));
1157         }
1158 
1159         ArrayLengthOp(ArrayLengthOp that, CodeContext cc) {
1160             super(that, cc);
1161         }
1162 
1163         @Override
1164         public ArrayLengthOp transform(CodeContext cc, CodeTransformer ot) {
1165             return new ArrayLengthOp(this, cc);
1166         }
1167 
1168         ArrayLengthOp(Value array) {
1169             super(List.of(array));
1170         }
1171 
1172         @Override
1173         public TypeElement resultType() {
1174             return INT;
1175         }
1176     }
1177 
1178     /**
1179      * The array access operation, that can model Java language array access expressions.
1180      */
1181     public sealed abstract static class ArrayAccessOp extends JavaOp
1182             implements AccessOp, ReflectiveOp {
1183 
1184         ArrayAccessOp(ArrayAccessOp that, CodeContext cc) {
1185             super(that, cc);
1186         }
1187 
1188         ArrayAccessOp(Value array, Value index, Value v) {
1189             super(operands(array, index, v));
1190         }
1191 
1192         static List<Value> operands(Value array, Value index, Value v) {
1193             return v == null
1194                     ? List.of(array, index)
1195                     : List.of(array, index, v);
1196         }
1197 
1198         /**
1199          * The array load operation, that can model Java language array expressions combined with load access to the
1200          * components of an array.
1201          */
1202         @OpDeclaration(ArrayLoadOp.NAME)
1203         public static final class ArrayLoadOp extends ArrayAccessOp
1204                 implements Pure, JavaExpression {
1205             static final String NAME = "array.load";
1206             final TypeElement componentType;
1207 
1208             ArrayLoadOp(ExternalizedOp def) {
1209                 if (def.operands().size() != 2) {
1210                     throw new IllegalArgumentException("Operation must have two operands");
1211                 }
1212 
1213                 this(def.operands().get(0), def.operands().get(1), def.resultType());
1214             }
1215 
1216             ArrayLoadOp(ArrayLoadOp that, CodeContext cc) {
1217                 super(that, cc);
1218                 this.componentType = that.componentType;
1219             }
1220 
1221             @Override
1222             public ArrayLoadOp transform(CodeContext cc, CodeTransformer ot) {
1223                 return new ArrayLoadOp(this, cc);
1224             }
1225 
1226             ArrayLoadOp(Value array, Value index) {
1227                 // @@@ revisit this when the component type is not explicitly given (see VarOp.resultType as an example)
1228                 this(array, index, ((ArrayType)array.type()).componentType());
1229             }
1230 
1231             ArrayLoadOp(Value array, Value index, TypeElement componentType) {
1232                 super(array, index, null);
1233                 this.componentType = componentType;
1234             }
1235 
1236             @Override
1237             public TypeElement resultType() {
1238                 return componentType;
1239             }
1240         }
1241 
1242         /**
1243          * The array store operation, that can model Java language array expressions combined with store access to the
1244          * components of an array.
1245          */
1246         @OpDeclaration(ArrayStoreOp.NAME)
1247         public static final class ArrayStoreOp extends ArrayAccessOp
1248                 implements JavaExpression, JavaStatement {
1249             static final String NAME = "array.store";
1250 
1251             ArrayStoreOp(ExternalizedOp def) {
1252                 if (def.operands().size() != 3) {
1253                     throw new IllegalArgumentException("Operation must have two operands");
1254                 }
1255 
1256                 this(def.operands().get(0), def.operands().get(1), def.operands().get(2));
1257             }
1258 
1259             ArrayStoreOp(ArrayStoreOp that, CodeContext cc) {
1260                 super(that, cc);
1261             }
1262 
1263             @Override
1264             public ArrayStoreOp transform(CodeContext cc, CodeTransformer ot) {
1265                 return new ArrayStoreOp(this, cc);
1266             }
1267 
1268             ArrayStoreOp(Value array, Value index, Value v) {
1269                 super(array, index, v);
1270             }
1271 
1272             @Override
1273             public TypeElement resultType() {
1274                 return VOID;
1275             }
1276         }
1277     }
1278 
1279     /**
1280      * The instanceof operation, that can model Java language instanceof expressions when the instanceof keyword is a
1281      * type comparison operator.
1282      */
1283     @OpDeclaration(InstanceOfOp.NAME)
1284     public static final class InstanceOfOp extends JavaOp
1285             implements Pure, ReflectiveOp, JavaExpression {
1286         static final String NAME = "instanceof";
1287         /** The externalized attribute key for the instanceof type descriptor. */
1288         public static final String ATTRIBUTE_TYPE_DESCRIPTOR = NAME + ".descriptor";
1289 
1290         final TypeElement typeDescriptor;
1291 
1292         InstanceOfOp(ExternalizedOp def) {
1293             if (def.operands().size() != 1) {
1294                 throw new IllegalArgumentException("Operation must have one operand " + def.name());
1295             }
1296 
1297             TypeElement typeDescriptor = def.extractAttributeValue(ATTRIBUTE_TYPE_DESCRIPTOR, true,
1298                     v -> switch (v) {
1299                         case JavaType td -> td;
1300                         case null, default -> throw new UnsupportedOperationException("Unsupported type descriptor value:" + v);
1301                     });
1302 
1303             this(typeDescriptor, def.operands().get(0));
1304         }
1305 
1306         InstanceOfOp(InstanceOfOp that, CodeContext cc) {
1307             super(that, cc);
1308 
1309             this.typeDescriptor = that.typeDescriptor;
1310         }
1311 
1312         @Override
1313         public InstanceOfOp transform(CodeContext cc, CodeTransformer ot) {
1314             return new InstanceOfOp(this, cc);
1315         }
1316 
1317         InstanceOfOp(TypeElement t, Value v) {
1318             super(List.of(v));
1319 
1320             this.typeDescriptor = t;
1321         }
1322 
1323         @Override
1324         public Map<String, Object> externalize() {
1325             return Map.of("", typeDescriptor);
1326         }
1327 
1328         /**
1329          * {@return the type descriptor associated with this instanceof operation}
1330          */
1331         public TypeElement type() {
1332             return typeDescriptor;
1333         }
1334 
1335         @Override
1336         public TypeElement resultType() {
1337             return BOOLEAN;
1338         }
1339     }
1340 
1341     /**
1342      * The cast operation, that can model Java language cast expressions for reference types.
1343      */
1344     @OpDeclaration(CastOp.NAME)
1345     public static final class CastOp extends JavaOp
1346             implements Pure, ReflectiveOp, JavaExpression {
1347         static final String NAME = "cast";
1348         /** The externalized attribute key for the cast type descriptor. */
1349         public static final String ATTRIBUTE_TYPE_DESCRIPTOR = NAME + ".descriptor";
1350 
1351         final TypeElement resultType;
1352         final TypeElement typeDescriptor;
1353 
1354         CastOp(ExternalizedOp def) {
1355             if (def.operands().size() != 1) {
1356                 throw new IllegalArgumentException("Operation must have one operand " + def.name());
1357             }
1358 
1359             TypeElement type = def.extractAttributeValue(ATTRIBUTE_TYPE_DESCRIPTOR, true,
1360                     v -> switch (v) {
1361                         case JavaType td -> td;
1362                         case null, default -> throw new UnsupportedOperationException("Unsupported type descriptor value:" + v);
1363                     });
1364 
1365             this(def.resultType(), type, def.operands().get(0));
1366         }
1367 
1368         CastOp(CastOp that, CodeContext cc) {
1369             super(that, cc);
1370 
1371             this.resultType = that.resultType;
1372             this.typeDescriptor = that.typeDescriptor;
1373         }
1374 
1375         @Override
1376         public CastOp transform(CodeContext cc, CodeTransformer ot) {
1377             return new CastOp(this, cc);
1378         }
1379 
1380         CastOp(TypeElement resultType, TypeElement t, Value v) {
1381             super(List.of(v));
1382 
1383             this.resultType = resultType;
1384             this.typeDescriptor = t;
1385         }
1386 
1387         @Override
1388         public Map<String, Object> externalize() {
1389             return Map.of("", typeDescriptor);
1390         }
1391 
1392         /**
1393          * {@return the type descriptor associated with this cast operation}
1394          */
1395         public TypeElement type() {
1396             return typeDescriptor;
1397         }
1398 
1399         @Override
1400         public TypeElement resultType() {
1401             return resultType;
1402         }
1403     }
1404 
1405     /**
1406      * The exception region start operation.
1407      */
1408     @OpDeclaration(ExceptionRegionEnter.NAME)
1409     public static final class ExceptionRegionEnter extends JavaOp
1410             implements BlockTerminating {
1411         static final String NAME = "exception.region.enter";
1412 
1413         // First successor is the non-exceptional successor whose target indicates
1414         // the first block in the exception region.
1415         // One or more subsequent successors target the exception catching blocks
1416         // each of which have one block argument whose type is an exception type.
1417         final List<Block.Reference> s;
1418 
1419         ExceptionRegionEnter(ExternalizedOp def) {
1420             this(def.successors());
1421         }
1422 
1423         ExceptionRegionEnter(ExceptionRegionEnter that, CodeContext cc) {
1424             super(that, cc);
1425 
1426             this.s = that.s.stream().map(cc::getSuccessorOrCreate).toList();
1427         }
1428 
1429         @Override
1430         public ExceptionRegionEnter transform(CodeContext cc, CodeTransformer ot) {
1431             return new ExceptionRegionEnter(this, cc);
1432         }
1433 
1434         ExceptionRegionEnter(List<Block.Reference> s) {
1435             super(List.of());
1436 
1437             if (s.size() < 2) {
1438                 throw new IllegalArgumentException("Operation must have two or more successors " + this);
1439             }
1440 
1441             this.s = List.copyOf(s);
1442         }
1443 
1444         @Override
1445         public List<Block.Reference> successors() {
1446             return s;
1447         }
1448 
1449         /**
1450          * {@return the starting block of this exception region}
1451          */
1452         public Block.Reference start() {
1453             return s.get(0);
1454         }
1455 
1456         /**
1457          * {@return the catch blocks for this exception region}
1458          */
1459         public List<Block.Reference> catchBlocks() {
1460             return s.subList(1, s.size());
1461         }
1462 
1463         @Override
1464         public TypeElement resultType() {
1465             return VOID;
1466         }
1467     }
1468 
1469     /**
1470      * The exception region end operation.
1471      */
1472     @OpDeclaration(ExceptionRegionExit.NAME)
1473     public static final class ExceptionRegionExit extends JavaOp
1474             implements BlockTerminating {
1475         static final String NAME = "exception.region.exit";
1476 
1477         // First successor is the non-exceptional successor whose target indicates
1478         // the first block following the exception region.
1479         final List<Block.Reference> s;
1480 
1481         ExceptionRegionExit(ExternalizedOp def) {
1482             this(def.successors());
1483         }
1484 
1485         ExceptionRegionExit(ExceptionRegionExit that, CodeContext cc) {
1486             super(that, cc);
1487 
1488             this.s = that.s.stream().map(cc::getSuccessorOrCreate).toList();
1489         }
1490 
1491         @Override
1492         public ExceptionRegionExit transform(CodeContext cc, CodeTransformer ot) {
1493             return new ExceptionRegionExit(this, cc);
1494         }
1495 
1496         ExceptionRegionExit(List<Block.Reference> s) {
1497             super(List.of());
1498 
1499             if (s.size() < 2) {
1500                 throw new IllegalArgumentException("Operation must have two or more successors " + this);
1501             }
1502 
1503             this.s = List.copyOf(s);
1504         }
1505 
1506         @Override
1507         public List<Block.Reference> successors() {
1508             return s;
1509         }
1510 
1511         /**
1512          * {@return the successor block that follows this exception region}
1513          */
1514         public Block.Reference end() {
1515             return s.get(0);
1516         }
1517 
1518         /**
1519          * {@return the catch blocks for this exception region}
1520          */
1521         public List<Block.Reference> catchBlocks() {
1522             return s.subList(1, s.size());
1523         }
1524 
1525         @Override
1526         public TypeElement resultType() {
1527             return VOID;
1528         }
1529     }
1530 
1531     /**
1532      * The String Concatenation Operation
1533      */
1534 
1535     @OpDeclaration(ConcatOp.NAME)
1536     public static final class ConcatOp extends JavaOp
1537             implements Pure, JavaExpression {
1538         static final String NAME = "concat";
1539 
1540         ConcatOp(ConcatOp that, CodeContext cc) {
1541             super(that, cc);
1542         }
1543 
1544         ConcatOp(ExternalizedOp def) {
1545             if (def.operands().size() != 2) {
1546                 throw new IllegalArgumentException("Concatenation Operation must have two operands.");
1547             }
1548 
1549             this(def.operands().get(0), def.operands().get(1));
1550         }
1551 
1552         ConcatOp(Value lhs, Value rhs) {
1553             super(List.of(lhs, rhs));
1554         }
1555 
1556         @Override
1557         public Op transform(CodeContext cc, CodeTransformer ot) {
1558             return new ConcatOp(this, cc);
1559         }
1560 
1561         @Override
1562         public TypeElement resultType() {
1563             return J_L_STRING;
1564         }
1565     }
1566 
1567     /**
1568      * The arithmetic operation.
1569      */
1570     public sealed static abstract class ArithmeticOperation extends JavaOp
1571             implements Pure, JavaExpression {
1572         ArithmeticOperation(ArithmeticOperation that, CodeContext cc) {
1573             super(that, cc);
1574         }
1575 
1576         ArithmeticOperation(List<Value> operands) {
1577             super(operands);
1578         }
1579     }
1580 
1581     /**
1582      * The test operation.
1583      */
1584     public sealed static abstract class TestOperation extends JavaOp
1585             implements Pure, JavaExpression {
1586         TestOperation(TestOperation that, CodeContext cc) {
1587             super(that, cc);
1588         }
1589 
1590         TestOperation(List<Value> operands) {
1591             super(operands);
1592         }
1593     }
1594 
1595     /**
1596      * The binary arithmetic operation.
1597      */
1598     public sealed static abstract class BinaryOp extends ArithmeticOperation {
1599         BinaryOp(BinaryOp that, CodeContext cc) {
1600             super(that, cc);
1601         }
1602 
1603         BinaryOp(Value lhs, Value rhs) {
1604             super(List.of(lhs, rhs));
1605         }
1606 
1607         @Override
1608         public TypeElement resultType() {
1609             return operands().get(0).type();
1610         }
1611     }
1612 
1613     /**
1614      * The unary arithmetic operation.
1615      */
1616     public sealed static abstract class UnaryOp extends ArithmeticOperation {
1617         UnaryOp(UnaryOp that, CodeContext cc) {
1618             super(that, cc);
1619         }
1620 
1621         UnaryOp(Value v) {
1622             super(List.of(v));
1623         }
1624 
1625         @Override
1626         public TypeElement resultType() {
1627             return operands().get(0).type();
1628         }
1629     }
1630 
1631     /**
1632      * The binary test operation.
1633      */
1634     public sealed static abstract class BinaryTestOp extends TestOperation {
1635         BinaryTestOp(BinaryTestOp that, CodeContext cc) {
1636             super(that, cc);
1637         }
1638 
1639         BinaryTestOp(Value lhs, Value rhs) {
1640             super(List.of(lhs, rhs));
1641         }
1642 
1643         @Override
1644         public TypeElement resultType() {
1645             return BOOLEAN;
1646         }
1647     }
1648 
1649     /**
1650      * The add operation, that can model the Java language binary {@code +} operator for numeric types
1651      */
1652     @OpDeclaration(AddOp.NAME)
1653     public static final class AddOp extends BinaryOp {
1654         static final String NAME = "add";
1655 
1656         AddOp(ExternalizedOp def) {
1657             this(def.operands().get(0), def.operands().get(1));
1658         }
1659 
1660         AddOp(AddOp that, CodeContext cc) {
1661             super(that, cc);
1662         }
1663 
1664         @Override
1665         public AddOp transform(CodeContext cc, CodeTransformer ot) {
1666             return new AddOp(this, cc);
1667         }
1668 
1669         AddOp(Value lhs, Value rhs) {
1670             super(lhs, rhs);
1671         }
1672     }
1673 
1674     /**
1675      * The sub operation, that can model the Java language binary {@code -} operator for numeric types
1676      */
1677     @OpDeclaration(SubOp.NAME)
1678     public static final class SubOp extends BinaryOp {
1679         static final String NAME = "sub";
1680 
1681         SubOp(ExternalizedOp def) {
1682             this(def.operands().get(0), def.operands().get(1));
1683         }
1684 
1685         SubOp(SubOp that, CodeContext cc) {
1686             super(that, cc);
1687         }
1688 
1689         @Override
1690         public SubOp transform(CodeContext cc, CodeTransformer ot) {
1691             return new SubOp(this, cc);
1692         }
1693 
1694         SubOp(Value lhs, Value rhs) {
1695             super(lhs, rhs);
1696         }
1697     }
1698 
1699     /**
1700      * The mul operation, that can model the Java language binary {@code *} operator for numeric types
1701      */
1702     @OpDeclaration(MulOp.NAME)
1703     public static final class MulOp extends BinaryOp {
1704         static final String NAME = "mul";
1705 
1706         MulOp(ExternalizedOp def) {
1707             this(def.operands().get(0), def.operands().get(1));
1708         }
1709 
1710         MulOp(MulOp that, CodeContext cc) {
1711             super(that, cc);
1712         }
1713 
1714         @Override
1715         public MulOp transform(CodeContext cc, CodeTransformer ot) {
1716             return new MulOp(this, cc);
1717         }
1718 
1719         MulOp(Value lhs, Value rhs) {
1720             super(lhs, rhs);
1721         }
1722     }
1723 
1724     /**
1725      * The div operation, that can model the Java language binary {@code /} operator for numeric types
1726      */
1727     @OpDeclaration(DivOp.NAME)
1728     public static final class DivOp extends BinaryOp {
1729         static final String NAME = "div";
1730 
1731         DivOp(ExternalizedOp def) {
1732             this(def.operands().get(0), def.operands().get(1));
1733         }
1734 
1735         DivOp(DivOp that, CodeContext cc) {
1736             super(that, cc);
1737         }
1738 
1739         @Override
1740         public DivOp transform(CodeContext cc, CodeTransformer ot) {
1741             return new DivOp(this, cc);
1742         }
1743 
1744         DivOp(Value lhs, Value rhs) {
1745             super(lhs, rhs);
1746         }
1747     }
1748 
1749     /**
1750      * The mod operation, that can model the Java language binary {@code %} operator for numeric types
1751      */
1752     @OpDeclaration(ModOp.NAME)
1753     public static final class ModOp extends BinaryOp {
1754         static final String NAME = "mod";
1755 
1756         ModOp(ExternalizedOp def) {
1757             this(def.operands().get(0), def.operands().get(1));
1758         }
1759 
1760         ModOp(ModOp that, CodeContext cc) {
1761             super(that, cc);
1762         }
1763 
1764         @Override
1765         public ModOp transform(CodeContext cc, CodeTransformer ot) {
1766             return new ModOp(this, cc);
1767         }
1768 
1769         ModOp(Value lhs, Value rhs) {
1770             super(lhs, rhs);
1771         }
1772     }
1773 
1774     /**
1775      * The bitwise/logical or operation, that can model the Java language binary {@code |} operator for integral types
1776      * and booleans
1777      */
1778     @OpDeclaration(OrOp.NAME)
1779     public static final class OrOp extends BinaryOp {
1780         static final String NAME = "or";
1781 
1782         OrOp(ExternalizedOp def) {
1783             this(def.operands().get(0), def.operands().get(1));
1784         }
1785 
1786         OrOp(OrOp that, CodeContext cc) {
1787             super(that, cc);
1788         }
1789 
1790         @Override
1791         public OrOp transform(CodeContext cc, CodeTransformer ot) {
1792             return new OrOp(this, cc);
1793         }
1794 
1795         OrOp(Value lhs, Value rhs) {
1796             super(lhs, rhs);
1797         }
1798     }
1799 
1800     /**
1801      * The bitwise/logical and operation, that can model the Java language binary {@code &} operator for integral types
1802      * and booleans
1803      */
1804     @OpDeclaration(AndOp.NAME)
1805     public static final class AndOp extends BinaryOp {
1806         static final String NAME = "and";
1807 
1808         AndOp(ExternalizedOp def) {
1809             this(def.operands().get(0), def.operands().get(1));
1810         }
1811 
1812         AndOp(AndOp that, CodeContext cc) {
1813             super(that, cc);
1814         }
1815 
1816         @Override
1817         public AndOp transform(CodeContext cc, CodeTransformer ot) {
1818             return new AndOp(this, cc);
1819         }
1820 
1821         AndOp(Value lhs, Value rhs) {
1822             super(lhs, rhs);
1823         }
1824     }
1825 
1826     /**
1827      * The xor operation, that can model the Java language binary {@code ^} operator for integral types
1828      * and booleans
1829      */
1830     @OpDeclaration(XorOp.NAME)
1831     public static final class XorOp extends BinaryOp {
1832         static final String NAME = "xor";
1833 
1834         XorOp(ExternalizedOp def) {
1835             this(def.operands().get(0), def.operands().get(1));
1836         }
1837 
1838         XorOp(XorOp that, CodeContext cc) {
1839             super(that, cc);
1840         }
1841 
1842         @Override
1843         public XorOp transform(CodeContext cc, CodeTransformer ot) {
1844             return new XorOp(this, cc);
1845         }
1846 
1847         XorOp(Value lhs, Value rhs) {
1848             super(lhs, rhs);
1849         }
1850     }
1851 
1852     /**
1853      * The (logical) shift left operation, that can model the Java language binary {@code <<} operator for integral types
1854      */
1855     @OpDeclaration(LshlOp.NAME)
1856     public static final class LshlOp extends BinaryOp {
1857         static final String NAME = "lshl";
1858 
1859         LshlOp(ExternalizedOp def) {
1860             this(def.operands().get(0), def.operands().get(1));
1861         }
1862 
1863         LshlOp(LshlOp that, CodeContext cc) {
1864             super(that, cc);
1865         }
1866 
1867         @Override
1868         public LshlOp transform(CodeContext cc, CodeTransformer ot) {
1869             return new LshlOp(this, cc);
1870         }
1871 
1872         LshlOp(Value lhs, Value rhs) {
1873             super(lhs, rhs);
1874         }
1875     }
1876 
1877     /**
1878      * The (arithmetic) shift right operation, that can model the Java language binary {@code >>} operator for integral types
1879      */
1880     @OpDeclaration(AshrOp.NAME)
1881     public static final class AshrOp extends JavaOp.BinaryOp {
1882         static final String NAME = "ashr";
1883 
1884         AshrOp(ExternalizedOp def) {
1885             this(def.operands().get(0), def.operands().get(1));
1886         }
1887 
1888         AshrOp(AshrOp that, CodeContext cc) {
1889             super(that, cc);
1890         }
1891 
1892         @Override
1893         public AshrOp transform(CodeContext cc, CodeTransformer ot) {
1894             return new AshrOp(this, cc);
1895         }
1896 
1897         AshrOp(Value lhs, Value rhs) {
1898             super(lhs, rhs);
1899         }
1900     }
1901 
1902     /**
1903      * The unsigned (logical) shift right operation, that can model the Java language binary {@code >>>} operator for integral types
1904      */
1905     @OpDeclaration(LshrOp.NAME)
1906     public static final class LshrOp extends JavaOp.BinaryOp {
1907         static final String NAME = "lshr";
1908 
1909         LshrOp(ExternalizedOp def) {
1910             this(def.operands().get(0), def.operands().get(1));
1911         }
1912 
1913         LshrOp(LshrOp that, CodeContext cc) {
1914             super(that, cc);
1915         }
1916 
1917         @Override
1918         public LshrOp transform(CodeContext cc, CodeTransformer ot) {
1919             return new LshrOp(this, cc);
1920         }
1921 
1922         LshrOp(Value lhs, Value rhs) {
1923             super(lhs, rhs);
1924         }
1925     }
1926 
1927     /**
1928      * The neg operation, that can model the Java language unary {@code -} operator for numeric types
1929      */
1930     @OpDeclaration(NegOp.NAME)
1931     public static final class NegOp extends UnaryOp {
1932         static final String NAME = "neg";
1933 
1934         NegOp(ExternalizedOp def) {
1935             this(def.operands().get(0));
1936         }
1937 
1938         NegOp(NegOp that, CodeContext cc) {
1939             super(that, cc);
1940         }
1941 
1942         @Override
1943         public NegOp transform(CodeContext cc, CodeTransformer ot) {
1944             return new NegOp(this, cc);
1945         }
1946 
1947         NegOp(Value v) {
1948             super(v);
1949         }
1950     }
1951 
1952     /**
1953      * The bitwise complement operation, that can model the Java language unary {@code ~} operator for integral types
1954      */
1955     @OpDeclaration(ComplOp.NAME)
1956     public static final class ComplOp extends UnaryOp {
1957         static final String NAME = "compl";
1958 
1959         ComplOp(ExternalizedOp def) {
1960             this(def.operands().get(0));
1961         }
1962 
1963         ComplOp(ComplOp that, CodeContext cc) {
1964             super(that, cc);
1965         }
1966 
1967         @Override
1968         public ComplOp transform(CodeContext cc, CodeTransformer ot) {
1969             return new ComplOp(this, cc);
1970         }
1971 
1972         ComplOp(Value v) {
1973             super(v);
1974         }
1975     }
1976 
1977     /**
1978      * The not operation, that can model the Java language unary {@code !} operator for boolean types
1979      */
1980     @OpDeclaration(NotOp.NAME)
1981     public static final class NotOp extends UnaryOp {
1982         static final String NAME = "not";
1983 
1984         NotOp(ExternalizedOp def) {
1985             this(def.operands().get(0));
1986         }
1987 
1988         NotOp(NotOp that, CodeContext cc) {
1989             super(that, cc);
1990         }
1991 
1992         @Override
1993         public NotOp transform(CodeContext cc, CodeTransformer ot) {
1994             return new NotOp(this, cc);
1995         }
1996 
1997         NotOp(Value v) {
1998             super(v);
1999         }
2000     }
2001 
2002     /**
2003      * The equals operation, that can model the Java language equality {@code ==} operator for numeric, boolean
2004      * and reference types
2005      */
2006     @OpDeclaration(EqOp.NAME)
2007     public static final class EqOp extends BinaryTestOp {
2008         static final String NAME = "eq";
2009 
2010         EqOp(ExternalizedOp def) {
2011             this(def.operands().get(0), def.operands().get(1));
2012         }
2013 
2014         EqOp(EqOp that, CodeContext cc) {
2015             super(that, cc);
2016         }
2017 
2018         @Override
2019         public EqOp transform(CodeContext cc, CodeTransformer ot) {
2020             return new EqOp(this, cc);
2021         }
2022 
2023         EqOp(Value lhs, Value rhs) {
2024             super(lhs, rhs);
2025         }
2026     }
2027 
2028     /**
2029      * The not equals operation, that can model the Java language equality {@code !=} operator for numeric, boolean
2030      * and reference types
2031      */
2032     @OpDeclaration(NeqOp.NAME)
2033     public static final class NeqOp extends BinaryTestOp {
2034         static final String NAME = "neq";
2035 
2036         NeqOp(ExternalizedOp def) {
2037             this(def.operands().get(0), def.operands().get(1));
2038         }
2039 
2040         NeqOp(NeqOp that, CodeContext cc) {
2041             super(that, cc);
2042         }
2043 
2044         @Override
2045         public NeqOp transform(CodeContext cc, CodeTransformer ot) {
2046             return new NeqOp(this, cc);
2047         }
2048 
2049         NeqOp(Value lhs, Value rhs) {
2050             super(lhs, rhs);
2051         }
2052     }
2053 
2054     /**
2055      * The greater than operation, that can model the Java language relational {@code >} operator for numeric types
2056      */
2057     @OpDeclaration(GtOp.NAME)
2058     public static final class GtOp extends BinaryTestOp {
2059         static final String NAME = "gt";
2060 
2061         GtOp(ExternalizedOp def) {
2062             this(def.operands().get(0), def.operands().get(1));
2063         }
2064 
2065         GtOp(GtOp that, CodeContext cc) {
2066             super(that, cc);
2067         }
2068 
2069         @Override
2070         public GtOp transform(CodeContext cc, CodeTransformer ot) {
2071             return new GtOp(this, cc);
2072         }
2073 
2074         GtOp(Value lhs, Value rhs) {
2075             super(lhs, rhs);
2076         }
2077     }
2078 
2079     /**
2080      * The greater than or equal to operation, that can model the Java language relational {@code >=} operator for
2081      * numeric types
2082      */
2083     @OpDeclaration(GeOp.NAME)
2084     public static final class GeOp extends BinaryTestOp {
2085         static final String NAME = "ge";
2086 
2087         GeOp(ExternalizedOp def) {
2088             this(def.operands().get(0), def.operands().get(1));
2089         }
2090 
2091         GeOp(GeOp that, CodeContext cc) {
2092             super(that, cc);
2093         }
2094 
2095         @Override
2096         public GeOp transform(CodeContext cc, CodeTransformer ot) {
2097             return new GeOp(this, cc);
2098         }
2099 
2100         GeOp(Value lhs, Value rhs) {
2101             super(lhs, rhs);
2102         }
2103     }
2104 
2105     /**
2106      * The less than operation, that can model the Java language relational {@code <} operator for
2107      * numeric types
2108      */
2109     @OpDeclaration(LtOp.NAME)
2110     public static final class LtOp extends BinaryTestOp {
2111         static final String NAME = "lt";
2112 
2113         LtOp(ExternalizedOp def) {
2114             this(def.operands().get(0), def.operands().get(1));
2115         }
2116 
2117         LtOp(LtOp that, CodeContext cc) {
2118             super(that, cc);
2119         }
2120 
2121         @Override
2122         public LtOp transform(CodeContext cc, CodeTransformer ot) {
2123             return new LtOp(this, cc);
2124         }
2125 
2126         LtOp(Value lhs, Value rhs) {
2127             super(lhs, rhs);
2128         }
2129     }
2130 
2131     /**
2132      * The less than or equal to operation, that can model the Java language relational {@code <=} operator for
2133      * numeric types
2134      */
2135     @OpDeclaration(LeOp.NAME)
2136     public static final class LeOp extends BinaryTestOp {
2137         static final String NAME = "le";
2138 
2139         LeOp(ExternalizedOp def) {
2140             this(def.operands().get(0), def.operands().get(1));
2141         }
2142 
2143         LeOp(LeOp that, CodeContext cc) {
2144             super(that, cc);
2145         }
2146 
2147         @Override
2148         public LeOp transform(CodeContext cc, CodeTransformer ot) {
2149             return new LeOp(this, cc);
2150         }
2151 
2152         LeOp(Value lhs, Value rhs) {
2153             super(lhs, rhs);
2154         }
2155     }
2156 
2157     /**
2158      * The label operation, that can model Java language statements with label identifiers.
2159      */
2160     public sealed static abstract class JavaLabelOp extends JavaOp
2161             implements Op.Lowerable, Op.BodyTerminating, JavaStatement {
2162         JavaLabelOp(JavaLabelOp that, CodeContext cc) {
2163             super(that, cc);
2164         }
2165 
2166         JavaLabelOp(Value label) {
2167             super(checkLabel(label));
2168         }
2169 
2170         static List<Value> checkLabel(Value label) {
2171             return label == null ? List.of() : List.of(label);
2172         }
2173 
2174         Op innerMostEnclosingTarget() {
2175             /*
2176                 A break statement with no label attempts to transfer control to the
2177                 innermost enclosing switch, while, do, or for statement; this enclosing statement,
2178                 which is called the break target, then immediately completes normally.
2179 
2180                 A break statement with label Identifier attempts to transfer control to the
2181                 enclosing labeled statement (14.7) that has the same Identifier as its label;
2182                 this enclosing statement, which is called the break target, then immediately completes normally.
2183                 In this case, the break target need not be a switch, while, do, or for statement.
2184              */
2185 
2186             // No label
2187             // Get innermost enclosing loop operation
2188             Op op = this;
2189             Body b;
2190             do {
2191                 b = op.ancestorBody();
2192                 op = b.ancestorOp();
2193                 if (op == null) {
2194                     throw new IllegalStateException("No enclosing loop");
2195                 }
2196             } while (!(op instanceof Op.Loop || op instanceof SwitchStatementOp));
2197 
2198             return switch (op) {
2199                 case Op.Loop lop -> lop.loopBody() == b ? op : null;
2200                 case SwitchStatementOp swStat -> swStat.bodies().contains(b) ? op : null;
2201                 default -> throw new IllegalStateException();
2202             };
2203         }
2204 
2205         boolean isUnlabeled() {
2206             return operands().isEmpty();
2207         }
2208 
2209         Op target() {
2210             // If unlabeled then find the nearest enclosing op
2211             // Otherwise obtain the label target
2212             if (isUnlabeled()) {
2213                 return innerMostEnclosingTarget();
2214             }
2215 
2216             Value value = operands().get(0);
2217             if (value instanceof Result r && r.op().ancestorOp() instanceof LabeledOp lop) {
2218                 return lop.target();
2219             } else {
2220                 throw new IllegalStateException("Bad label value: " + value + " " + ((Result) value).op());
2221             }
2222         }
2223 
2224         Block.Builder lower(Block.Builder b, Function<BranchTarget, Block.Builder> f) {
2225             Op opt = target();
2226             BranchTarget t = BranchTarget.getBranchTarget(b.context(), opt);
2227             if (t != null) {
2228                 b.op(branch(f.apply(t).successor()));
2229             } else {
2230                 throw new IllegalStateException("No branch target for operation: " + opt);
2231             }
2232             return b;
2233         }
2234 
2235         @Override
2236         public TypeElement resultType() {
2237             return VOID;
2238         }
2239     }
2240 
2241     /**
2242      * The break operation, that can model Java language break statements with label identifiers.
2243      */
2244     @OpDeclaration(BreakOp.NAME)
2245     public static final class BreakOp extends JavaLabelOp {
2246         static final String NAME = "java.break";
2247 
2248         BreakOp(ExternalizedOp def) {
2249             this(def.operands().isEmpty() ? null : def.operands().get(0));
2250         }
2251 
2252         BreakOp(BreakOp that, CodeContext cc) {
2253             super(that, cc);
2254         }
2255 
2256         @Override
2257         public BreakOp transform(CodeContext cc, CodeTransformer ot) {
2258             return new BreakOp(this, cc);
2259         }
2260 
2261         BreakOp(Value label) {
2262             super(label);
2263         }
2264 
2265         @Override
2266         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
2267             return lower(b, BranchTarget::breakBlock);
2268         }
2269     }
2270 
2271     /**
2272      * The continue operation, that can model Java language continue statements with label identifiers.
2273      */
2274     @OpDeclaration(ContinueOp.NAME)
2275     public static final class ContinueOp extends JavaLabelOp {
2276         static final String NAME = "java.continue";
2277 
2278         ContinueOp(ExternalizedOp def) {
2279             this(def.operands().isEmpty() ? null : def.operands().get(0));
2280         }
2281 
2282         ContinueOp(ContinueOp that, CodeContext cc) {
2283             super(that, cc);
2284         }
2285 
2286         @Override
2287         public ContinueOp transform(CodeContext cc, CodeTransformer ot) {
2288             return new ContinueOp(this, cc);
2289         }
2290 
2291         ContinueOp(Value label) {
2292             super(label);
2293         }
2294 
2295         @Override
2296         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
2297             return lower(b, BranchTarget::continueBlock);
2298         }
2299     }
2300 
2301     /**
2302      * The yield operation, that can model Java language yield statements.
2303      */
2304     @OpDeclaration(YieldOp.NAME)
2305     public static final class YieldOp extends JavaOp
2306             implements Op.BodyTerminating, JavaStatement, Op.Lowerable {
2307         static final String NAME = "java.yield";
2308 
2309         YieldOp(ExternalizedOp def) {
2310             if (def.operands().size() > 1) {
2311                 throw new IllegalArgumentException("Operation must have zero or one operand " + def.name());
2312             }
2313 
2314             this(def.operands().isEmpty() ? null : def.operands().get(0));
2315         }
2316 
2317         YieldOp(YieldOp that, CodeContext cc) {
2318             super(that, cc);
2319         }
2320 
2321         @Override
2322         public YieldOp transform(CodeContext cc, CodeTransformer ot) {
2323             return new YieldOp(this, cc);
2324         }
2325 
2326         YieldOp(Value operand) {
2327             super(operand == null ? List.of() : List.of(operand));
2328         }
2329 
2330         /**
2331          * {@return the yielded value, or {@code null} if there is no yielded value}
2332          */
2333         public Value yieldValue() {
2334             if (operands().size() == 1) {
2335                 return operands().get(0);
2336             } else {
2337                 // @@@
2338                 return null;
2339             }
2340         }
2341 
2342         @Override
2343         public TypeElement resultType() {
2344             return VOID;
2345         }
2346 
2347         @Override
2348         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
2349             // for now, we will use breakBlock field to indicate java.yield target block
2350             return lower(b, BranchTarget::breakBlock);
2351         }
2352 
2353         Block.Builder lower(Block.Builder b, Function<BranchTarget, Block.Builder> f) {
2354             Op opt = target();
2355             BranchTarget t = BranchTarget.getBranchTarget(b.context(), opt);
2356             if (t != null) {
2357                 b.op(branch(f.apply(t).successor(b.context().getValue(yieldValue()))));
2358             } else {
2359                 throw new IllegalStateException("No branch target for operation: " + opt);
2360             }
2361             return b;
2362         }
2363 
2364         Op target() {
2365             return innerMostEnclosingTarget();
2366         }
2367 
2368         Op innerMostEnclosingTarget() {
2369             Op op = this;
2370             Body b;
2371             do {
2372                 b = op.ancestorBody();
2373                 op = b.ancestorOp();
2374                 if (op == null) {
2375                     throw new IllegalStateException("No enclosing switch");
2376                 }
2377             } while (!(op instanceof SwitchExpressionOp));
2378             return op;
2379         }
2380     }
2381 
2382     /**
2383      * The block operation, that can model Java language blocks.
2384      */
2385     @OpDeclaration(BlockOp.NAME)
2386     public static final class BlockOp extends JavaOp
2387             implements Op.Nested, Op.Lowerable, JavaStatement {
2388         static final String NAME = "java.block";
2389 
2390         final Body body;
2391 
2392         BlockOp(ExternalizedOp def) {
2393             if (!def.operands().isEmpty()) {
2394                 throw new IllegalStateException("Operation must have no operands");
2395             }
2396 
2397             this(def.bodyDefinitions().get(0));
2398         }
2399 
2400         BlockOp(BlockOp that, CodeContext cc, CodeTransformer ot) {
2401             super(that, cc);
2402 
2403             // Copy body
2404             this.body = that.body.transform(cc, ot).build(this);
2405         }
2406 
2407         @Override
2408         public BlockOp transform(CodeContext cc, CodeTransformer ot) {
2409             return new BlockOp(this, cc, ot);
2410         }
2411 
2412         BlockOp(Body.Builder bodyC) {
2413             super(List.of());
2414 
2415             this.body = bodyC.build(this);
2416             if (!body.bodyType().returnType().equals(VOID)) {
2417                 throw new IllegalArgumentException("Body should return void: " + body.bodyType());
2418             }
2419             if (!body.bodyType().parameterTypes().isEmpty()) {
2420                 throw new IllegalArgumentException("Body should have zero parameters: " + body.bodyType());
2421             }
2422         }
2423 
2424         @Override
2425         public List<Body> bodies() {
2426             return List.of(body);
2427         }
2428 
2429         /**
2430          * {@return the block operation body}
2431          */
2432         public Body body() {
2433             return body;
2434         }
2435 
2436         @Override
2437         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
2438             Block.Builder exit = b.block();
2439             BranchTarget.setBranchTarget(b.context(), this, exit, null);
2440 
2441             b.body(body, List.of(), andThenLowering(opT, (block, op) -> {
2442                 if (op instanceof CoreOp.YieldOp) {
2443                     block.op(branch(exit.successor()));
2444                     return block;
2445                 } else {
2446                     return null;
2447                 }
2448             }));
2449 
2450             return exit;
2451         }
2452 
2453         @Override
2454         public TypeElement resultType() {
2455             return VOID;
2456         }
2457     }
2458 
2459     /**
2460      * The synchronized operation, that can model Java synchronized statements.
2461      */
2462     @OpDeclaration(SynchronizedOp.NAME)
2463     public static final class SynchronizedOp extends JavaOp
2464             implements Op.Nested, Op.Lowerable, JavaStatement {
2465         static final String NAME = "java.synchronized";
2466 
2467         final Body expr;
2468         final Body blockBody;
2469 
2470         SynchronizedOp(ExternalizedOp def) {
2471             this(def.bodyDefinitions().get(0), def.bodyDefinitions().get(1));
2472         }
2473 
2474         SynchronizedOp(SynchronizedOp that, CodeContext cc, CodeTransformer ot) {
2475             super(that, cc);
2476 
2477             // Copy bodies
2478             this.expr = that.expr.transform(cc, ot).build(this);
2479             this.blockBody = that.blockBody.transform(cc, ot).build(this);
2480         }
2481 
2482         @Override
2483         public SynchronizedOp transform(CodeContext cc, CodeTransformer ot) {
2484             return new SynchronizedOp(this, cc, ot);
2485         }
2486 
2487         SynchronizedOp(Body.Builder exprC, Body.Builder bodyC) {
2488             super(List.of());
2489 
2490             this.expr = exprC.build(this);
2491             if (expr.bodyType().returnType().equals(VOID)) {
2492                 throw new IllegalArgumentException("Expression body should return non-void value: " + expr.bodyType());
2493             }
2494             if (!expr.bodyType().parameterTypes().isEmpty()) {
2495                 throw new IllegalArgumentException("Expression body should have zero parameters: " + expr.bodyType());
2496             }
2497 
2498             this.blockBody = bodyC.build(this);
2499             if (!blockBody.bodyType().returnType().equals(VOID)) {
2500                 throw new IllegalArgumentException("Block body should return void: " + blockBody.bodyType());
2501             }
2502             if (!blockBody.bodyType().parameterTypes().isEmpty()) {
2503                 throw new IllegalArgumentException("Block body should have zero parameters: " + blockBody.bodyType());
2504             }
2505         }
2506 
2507         @Override
2508         public List<Body> bodies() {
2509             return List.of(expr, blockBody);
2510         }
2511 
2512         /**
2513          * {@return the expression body whose result is the monitor object for synchronization}
2514          */
2515         public Body expr() {
2516             return expr;
2517         }
2518 
2519         /**
2520          * {@return the body that is executed within the synchronized block}
2521          */
2522         public Body blockBody() {
2523             return blockBody;
2524         }
2525 
2526         @Override
2527         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
2528             // Lower the expression body, yielding a monitor target
2529             b = lowerExpr(b, opT);
2530             Value monitorTarget = b.parameters().get(0);
2531 
2532             // Monitor enter
2533             b.op(monitorEnter(monitorTarget));
2534 
2535             Block.Builder exit = b.block();
2536             BranchTarget.setBranchTarget(b.context(), this, exit, null);
2537 
2538             // Exception region for the body
2539             Block.Builder syncRegionEnter = b.block();
2540             Block.Builder catcherFinally = b.block();
2541             b.op(exceptionRegionEnter(
2542                     syncRegionEnter.successor(), catcherFinally.successor()));
2543 
2544             CodeTransformer syncExitTransformer = compose(opT, (block, op) -> {
2545                 if (op instanceof CoreOp.ReturnOp ||
2546                     (op instanceof JavaOp.JavaLabelOp lop && ifExitFromSynchronized(lop))) {
2547                     // Monitor exit
2548                     block.op(monitorExit(monitorTarget));
2549                     // Exit the exception region
2550                     Block.Builder exitRegion = block.block();
2551                     block.op(exceptionRegionExit(exitRegion.successor(), catcherFinally.successor()));
2552                     return exitRegion;
2553                 } else {
2554                     return block;
2555                 }
2556             });
2557 
2558             syncRegionEnter.body(blockBody, List.of(), andThenLowering(syncExitTransformer, (block, op) -> {
2559                 if (op instanceof CoreOp.YieldOp) {
2560                     // Monitor exit
2561                     block.op(monitorExit(monitorTarget));
2562                     // Exit the exception region
2563                     block.op(exceptionRegionExit(exit.successor(), catcherFinally.successor()));
2564                     return block;
2565                 } else {
2566                     return null;
2567                 }
2568             }));
2569 
2570             // The catcher, with an exception region back branching to itself
2571             Block.Builder catcherFinallyRegionEnter = b.block();
2572             catcherFinally.op(exceptionRegionEnter(
2573                     catcherFinallyRegionEnter.successor(), catcherFinally.successor()));
2574 
2575             // Monitor exit
2576             catcherFinallyRegionEnter.op(monitorExit(monitorTarget));
2577             Block.Builder catcherFinallyRegionExit = b.block();
2578             // Exit the exception region
2579             catcherFinallyRegionEnter.op(exceptionRegionExit(
2580                     catcherFinallyRegionExit.successor(), catcherFinally.successor()));
2581             // Rethrow outside of region
2582             Block.Parameter t = catcherFinally.parameter(type(Throwable.class));
2583             catcherFinallyRegionExit.op(throw_(t));
2584 
2585             return exit;
2586         }
2587 
2588         Block.Builder lowerExpr(Block.Builder b, CodeTransformer opT) {
2589             Block.Builder exprExit = b.block(expr.bodyType().returnType());
2590             b.body(expr, List.of(), andThenLowering(opT, (block, op) -> {
2591                 if (op instanceof CoreOp.YieldOp yop) {
2592                     Value monitorTarget = block.context().getValue(yop.yieldValue());
2593                     block.op(branch(exprExit.successor(monitorTarget)));
2594                     return block;
2595                 } else {
2596                     return null;
2597                 }
2598             }));
2599             return exprExit;
2600         }
2601 
2602         boolean ifExitFromSynchronized(JavaLabelOp lop) {
2603             Op target = lop.target();
2604             return target == this || target.isAncestorOf(this);
2605         }
2606 
2607         @Override
2608         public TypeElement resultType() {
2609             return VOID;
2610         }
2611     }
2612 
2613     /**
2614      * The labeled operation, that can model Java language labeled statements.
2615      */
2616     @OpDeclaration(LabeledOp.NAME)
2617     public static final class LabeledOp extends JavaOp
2618             implements Op.Nested, Op.Lowerable, JavaStatement {
2619         static final String NAME = "java.labeled";
2620 
2621         final Body body;
2622 
2623         LabeledOp(ExternalizedOp def) {
2624             if (!def.operands().isEmpty()) {
2625                 throw new IllegalStateException("Operation must have no operands");
2626             }
2627 
2628             this(def.bodyDefinitions().get(0));
2629         }
2630 
2631         LabeledOp(LabeledOp that, CodeContext cc, CodeTransformer ot) {
2632             super(that, cc);
2633 
2634             // Copy body
2635             this.body = that.body.transform(cc, ot).build(this);
2636         }
2637 
2638         @Override
2639         public LabeledOp transform(CodeContext cc, CodeTransformer ot) {
2640             return new LabeledOp(this, cc, ot);
2641         }
2642 
2643         LabeledOp(Body.Builder bodyC) {
2644             super(List.of());
2645 
2646             this.body = bodyC.build(this);
2647             if (!body.bodyType().returnType().equals(VOID)) {
2648                 throw new IllegalArgumentException("Body should return void: " + body.bodyType());
2649             }
2650             if (!body.bodyType().parameterTypes().isEmpty()) {
2651                 throw new IllegalArgumentException("Body should have zero parameters: " + body.bodyType());
2652             }
2653         }
2654 
2655         @Override
2656         public List<Body> bodies() {
2657             return List.of(body);
2658         }
2659 
2660         /**
2661          * {@return the label associated with this labelled operation}
2662          */
2663         public Op label() {
2664             return body.entryBlock().firstOp();
2665         }
2666 
2667         /**
2668          * {@return the first operation associated with this labelled operation}
2669          */
2670         public Op target() {
2671             return body.entryBlock().nextOp(label());
2672         }
2673 
2674         @Override
2675         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
2676             Block.Builder exit = b.block();
2677             BranchTarget.setBranchTarget(b.context(), this, exit, null);
2678 
2679             AtomicBoolean first = new AtomicBoolean();
2680             b.body(body, List.of(), andThenLowering(opT, (block, op) -> {
2681                 // Drop first operation that corresponds to the label
2682                 if (!first.get()) {
2683                     first.set(true);
2684                     return block;
2685                 }
2686 
2687                 if (op instanceof CoreOp.YieldOp) {
2688                     block.op(branch(exit.successor()));
2689                     return block;
2690                 } else {
2691                     return null;
2692                 }
2693             }));
2694 
2695             return exit;
2696         }
2697 
2698         @Override
2699         public TypeElement resultType() {
2700             return VOID;
2701         }
2702     }
2703 
2704     /**
2705      * The if operation, that can model Java language if, if-then, and if-then-else statements.
2706      */
2707     @OpDeclaration(IfOp.NAME)
2708     public static final class IfOp extends JavaOp
2709             implements Op.Nested, Op.Lowerable, JavaStatement {
2710 
2711         static final FunctionType PREDICATE_TYPE = CoreType.functionType(BOOLEAN);
2712 
2713         static final FunctionType ACTION_TYPE = CoreType.FUNCTION_TYPE_VOID;
2714 
2715         /**
2716          * Builder for the initial condition branch of an if operation.
2717          */
2718         public static class IfBuilder {
2719             final Body.Builder ancestorBody;
2720             final List<Body.Builder> bodies;
2721 
2722             IfBuilder(Body.Builder ancestorBody) {
2723                 this.ancestorBody = ancestorBody;
2724                 this.bodies = new ArrayList<>();
2725             }
2726 
2727             /**
2728              * Begins an if-branch with the given predicate builder.
2729              * @param c a consumer for the predicate body
2730              * @return a then-branch builder to complete the if operation
2731              */
2732             public ThenBuilder if_(Consumer<Block.Builder> c) {
2733                 Body.Builder body = Body.Builder.of(ancestorBody, PREDICATE_TYPE);
2734                 c.accept(body.entryBlock());
2735                 bodies.add(body);
2736 
2737                 return new ThenBuilder(ancestorBody, bodies);
2738             }
2739         }
2740 
2741         /**
2742          * Builder for the then-branch of an if operation.
2743          */
2744         public static class ThenBuilder {
2745             final Body.Builder ancestorBody;
2746             final List<Body.Builder> bodies;
2747 
2748             ThenBuilder(Body.Builder ancestorBody, List<Body.Builder> bodies) {
2749                 this.ancestorBody = ancestorBody;
2750                 this.bodies = bodies;
2751             }
2752 
2753             /**
2754              * Adds a then-branch with the given body.
2755              * @param c a consumer for the then-branch body
2756              * @return a builder for further else-if-/else-branches
2757              */
2758             public ElseIfBuilder then(Consumer<Block.Builder> c) {
2759                 Body.Builder body = Body.Builder.of(ancestorBody, ACTION_TYPE);
2760                 c.accept(body.entryBlock());
2761                 bodies.add(body);
2762 
2763                 return new ElseIfBuilder(ancestorBody, bodies);
2764             }
2765 
2766             /**
2767              * Adds a then-branch with an empty body.
2768              * @return a builder for further else-if-/else-branches
2769              */
2770             public ElseIfBuilder then() {
2771                 Body.Builder body = Body.Builder.of(ancestorBody, ACTION_TYPE);
2772                 body.entryBlock().op(core_yield());
2773                 bodies.add(body);
2774 
2775                 return new ElseIfBuilder(ancestorBody, bodies);
2776             }
2777         }
2778 
2779         /**
2780          * Builder for additional else-if-/else-branches of an if operation.
2781          */
2782         public static class ElseIfBuilder {
2783             final Body.Builder ancestorBody;
2784             final List<Body.Builder> bodies;
2785 
2786             ElseIfBuilder(Body.Builder ancestorBody, List<Body.Builder> bodies) {
2787                 this.ancestorBody = ancestorBody;
2788                 this.bodies = bodies;
2789             }
2790 
2791             /**
2792              * Adds an else-if-branch to the if operation.
2793              * @param c a consumer for the else-if predicate
2794              * @return a then-branch builder for the else-if body
2795              */
2796             public ThenBuilder elseif(Consumer<Block.Builder> c) {
2797                 Body.Builder body = Body.Builder.of(ancestorBody, PREDICATE_TYPE);
2798                 c.accept(body.entryBlock());
2799                 bodies.add(body);
2800 
2801                 return new ThenBuilder(ancestorBody, bodies);
2802             }
2803 
2804             /**
2805              * Adds an else-branch with the given body.
2806              * @param c a consumer for the else-branch body
2807              * @return the completed if operation
2808              */
2809             public IfOp else_(Consumer<Block.Builder> c) {
2810                 Body.Builder body = Body.Builder.of(ancestorBody, ACTION_TYPE);
2811                 c.accept(body.entryBlock());
2812                 bodies.add(body);
2813 
2814                 return new IfOp(bodies);
2815             }
2816 
2817             /**
2818              * Adds an else-branch with an empty body.
2819              * @return the completed if operation
2820              */
2821             public IfOp else_() {
2822                 Body.Builder body = Body.Builder.of(ancestorBody, ACTION_TYPE);
2823                 body.entryBlock().op(core_yield());
2824                 bodies.add(body);
2825 
2826                 return new IfOp(bodies);
2827             }
2828         }
2829 
2830         static final String NAME = "java.if";
2831 
2832         final List<Body> bodies;
2833 
2834         IfOp(ExternalizedOp def) {
2835             if (!def.operands().isEmpty()) {
2836                 throw new IllegalStateException("Operation must have no operands");
2837             }
2838 
2839             this(def.bodyDefinitions());
2840         }
2841 
2842         IfOp(IfOp that, CodeContext cc, CodeTransformer ot) {
2843             super(that, cc);
2844 
2845             // Copy body
2846             this.bodies = that.bodies.stream()
2847                     .map(b -> b.transform(cc, ot).build(this)).toList();
2848         }
2849 
2850         @Override
2851         public IfOp transform(CodeContext cc, CodeTransformer ot) {
2852             return new IfOp(this, cc, ot);
2853         }
2854 
2855         IfOp(List<Body.Builder> bodyCs) {
2856             super(List.of());
2857 
2858             // Normalize by adding an empty else action
2859             // @@@ Is this needed?
2860             if (bodyCs.size() % 2 == 0) {
2861                 bodyCs = new ArrayList<>(bodyCs);
2862                 Body.Builder end = Body.Builder.of(bodyCs.get(0).ancestorBody(),
2863                         CoreType.FUNCTION_TYPE_VOID);
2864                 end.entryBlock().op(core_yield());
2865                 bodyCs.add(end);
2866             }
2867 
2868             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
2869 
2870             if (bodies.size() < 2) {
2871                 throw new IllegalArgumentException("Incorrect number of bodies: " + bodies.size());
2872             }
2873             for (int i = 0; i < bodies.size(); i += 2) {
2874                 Body action;
2875                 if (i == bodies.size() - 1) {
2876                     action = bodies.get(i);
2877                 } else {
2878                     action = bodies.get(i + 1);
2879                     Body fromPred = bodies.get(i);
2880                     if (!fromPred.bodyType().equals(CoreType.functionType(BOOLEAN))) {
2881                         throw new IllegalArgumentException("Illegal predicate body descriptor: " + fromPred.bodyType());
2882                     }
2883                 }
2884                 if (!action.bodyType().equals(CoreType.FUNCTION_TYPE_VOID)) {
2885                     throw new IllegalArgumentException("Illegal action body descriptor: " + action.bodyType());
2886                 }
2887             }
2888         }
2889 
2890         @Override
2891         public List<Body> bodies() {
2892             return bodies;
2893         }
2894 
2895         @Override
2896         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
2897             Block.Builder exit = b.block();
2898             BranchTarget.setBranchTarget(b.context(), this, exit, null);
2899 
2900             // Create predicate and action blocks
2901             List<Block.Builder> builders = new ArrayList<>();
2902             for (int i = 0; i < bodies.size(); i += 2) {
2903                 if (i == bodies.size() - 1) {
2904                     builders.add(b.block());
2905                 } else {
2906                     builders.add(i == 0 ? b : b.block());
2907                     builders.add(b.block());
2908                 }
2909             }
2910 
2911             for (int i = 0; i < bodies.size(); i += 2) {
2912                 Body actionBody;
2913                 Block.Builder action;
2914                 if (i == bodies.size() - 1) {
2915                     actionBody = bodies.get(i);
2916                     action = builders.get(i);
2917                 } else {
2918                     Body predBody = bodies.get(i);
2919                     actionBody = bodies.get(i + 1);
2920 
2921                     Block.Builder pred = builders.get(i);
2922                     action = builders.get(i + 1);
2923                     Block.Builder next = builders.get(i + 2);
2924 
2925                     pred.body(predBody, List.of(), andThenLowering(opT, (block, op) -> {
2926                         if (op instanceof CoreOp.YieldOp yo) {
2927                             block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
2928                                     action.successor(), next.successor()));
2929                             return block;
2930                         } else {
2931                             return null;
2932                         }
2933                     }));
2934                 }
2935 
2936                 action.body(actionBody, List.of(), andThenLowering(opT, (block, op) -> {
2937                     if (op instanceof CoreOp.YieldOp) {
2938                         block.op(branch(exit.successor()));
2939                         return block;
2940                     } else {
2941                         return null;
2942                     }
2943                 }));
2944             }
2945 
2946             return exit;
2947         }
2948 
2949         @Override
2950         public TypeElement resultType() {
2951             return VOID;
2952         }
2953     }
2954 
2955     /**
2956      * An operation modeling a Java switch statement or expression.
2957      */
2958     public abstract static sealed class JavaSwitchOp extends JavaOp implements Op.Nested, Op.Lowerable
2959             permits SwitchStatementOp, SwitchExpressionOp {
2960 
2961         final List<Body> bodies;
2962 
2963         JavaSwitchOp(JavaSwitchOp that, CodeContext cc, CodeTransformer ot) {
2964             super(that, cc);
2965 
2966             // Copy body
2967             this.bodies = that.bodies.stream()
2968                     .map(b -> b.transform(cc, ot).build(this)).toList();
2969         }
2970 
2971         JavaSwitchOp(Value target, List<Body.Builder> bodyCs) {
2972             super(List.of(target));
2973 
2974             // Each case is modelled as a contiguous pair of bodies
2975             // The first body models the case labels, and the second models the case statements
2976             // The labels body has a parameter whose type is target operand's type and returns a boolean value
2977             // The statements body has no parameters and returns void
2978             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
2979         }
2980 
2981         @Override
2982         public List<Body> bodies() {
2983             return bodies;
2984         }
2985 
2986         @Override
2987         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
2988             Value selectorExpression = b.context().getValue(operands().get(0));
2989 
2990             // @@@ we can add this during model generation
2991             // if no case null, add one that throws NPE
2992             if (!(selectorExpression.type() instanceof PrimitiveType) && !haveNullCase()) {
2993                 Block.Builder throwBlock = b.block();
2994                 throwBlock.op(throw_(
2995                         throwBlock.op(new_(MethodRef.constructor(NullPointerException.class)))
2996                 ));
2997 
2998                 Block.Builder continueBlock = b.block();
2999 
3000                 Result p = b.op(invoke(MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class),
3001                         selectorExpression, b.op(constant(J_L_OBJECT, null))));
3002                 b.op(conditionalBranch(p, throwBlock.successor(), continueBlock.successor()));
3003 
3004                 b = continueBlock;
3005             }
3006 
3007             int defLabelIndex = -1;
3008             for (int i = 0; i < bodies().size(); i+=2) {
3009                 Block eb = bodies().get(i).entryBlock();
3010                 // @@@ confusing YieldOp with Core.YieldOp in checks
3011                 if (eb.terminatingOp() instanceof CoreOp.YieldOp yop && yop.yieldValue() instanceof Op.Result r
3012                         && r.op() instanceof ConstantOp cop && cop.resultType().equals(BOOLEAN)) {
3013                     defLabelIndex = i;
3014                     break;
3015                 }
3016             }
3017             if (defLabelIndex == -1 && this instanceof SwitchExpressionOp) {
3018                 // if it's a switch expression, it must have a default
3019                 // if not explicit, it's an unconditional pattern which is the last label
3020                 defLabelIndex = bodies().size() - 2;
3021             }
3022 
3023             List<Block.Builder> blocks = new ArrayList<>();
3024             for (int i = 0; i < bodies().size(); i++) {
3025                 Block.Builder bb;
3026                 if (i == defLabelIndex) {
3027                     // we don't need a block for default label
3028                     bb = null;
3029                 } else {
3030                     bb = b.block();
3031                 }
3032                 blocks.add(bb);
3033             }
3034             // append ops of the first non default label to b
3035             for (int i = 0; i < blocks.size(); i+=2) {
3036                 if (blocks.get(i) == null) {
3037                     continue;
3038                 }
3039                 blocks.set(i, b);
3040                 break;
3041             }
3042 
3043             Block.Builder exit;
3044             if (bodies().isEmpty()) {
3045                 exit = b;
3046             } else {
3047                 exit = resultType() == VOID ? b.block() : b.block(resultType());
3048                 if (!exit.parameters().isEmpty()) {
3049                     exit.context().mapValue(result(), exit.parameters().get(0));
3050                 }
3051             }
3052 
3053             BranchTarget.setBranchTarget(b.context(), this, exit, null);
3054             // map statement body to nextExprBlock
3055             // this mapping will be used for lowering SwitchFallThroughOp
3056             for (int i = 1; i < bodies().size() - 2; i+=2) {
3057                 BranchTarget.setBranchTarget(b.context(), bodies().get(i), null, blocks.get(i + 2));
3058             }
3059 
3060             for (int i = 0; i < bodies().size(); i+=2) {
3061                 if (i == defLabelIndex) {
3062                     continue;
3063                 }
3064                 Block.Builder statement = blocks.get(i + 1);
3065                 boolean isLastLabel = i == blocks.size() - 2;
3066                 Block.Builder nextLabel = isLastLabel ? null : blocks.get(i + 2);
3067                 int finalDefLabelIndex = defLabelIndex;
3068                 blocks.get(i).body(bodies().get(i), List.of(selectorExpression), andThenLowering(opT,
3069                         (block, op) -> switch (op) {
3070                             case CoreOp.YieldOp yop -> {
3071                                 Block.Reference falseTarget;
3072                                 if (nextLabel != null) {
3073                                     falseTarget = nextLabel.successor();
3074                                 } else if (finalDefLabelIndex != -1) {
3075                                     falseTarget = blocks.get(finalDefLabelIndex + 1).successor();
3076                                 } else {
3077                                     falseTarget = exit.successor();
3078                                 }
3079                                 block.op(conditionalBranch(block.context().getValue(yop.yieldValue()),
3080                                         statement.successor(), falseTarget));
3081                                 yield block;
3082                             }
3083                             default -> null;
3084                         }));
3085 
3086                 blocks.get(i + 1).body(bodies().get(i + 1), List.of(), andThenLowering(opT,
3087                         (block, op) -> switch (op) {
3088                             case CoreOp.YieldOp yop -> {
3089                                 List<Value> args = yop.yieldValue() == null ? List.of() : List.of(block.context().getValue(yop.yieldValue()));
3090                                 block.op(branch(exit.successor(args)));
3091                                 yield block;
3092                             }
3093                             default -> null;
3094                         }));
3095             }
3096 
3097             if (defLabelIndex != -1) {
3098                 blocks.get(defLabelIndex + 1).body(bodies().get(defLabelIndex + 1), List.of(), andThenLowering(opT,
3099                         (block, op) -> switch (op) {
3100                             case CoreOp.YieldOp yop -> {
3101                                 List<Value> args = yop.yieldValue() == null ? List.of() : List.of(block.context().getValue(yop.yieldValue()));
3102                                 block.op(branch(exit.successor(args)));
3103                                 yield block;
3104                             }
3105                             default -> null;
3106                         }));
3107             }
3108 
3109             return exit;
3110         }
3111 
3112         boolean haveNullCase() {
3113             /*
3114             case null is modeled like this:
3115             (%4 : T)boolean -> {
3116                 %5 : java.lang.Object = constant @null;
3117                 %6 : boolean = invoke %4 %5 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
3118                 yield %6;
3119             }
3120             * */
3121             for (int i = 0; i < bodies().size() - 2; i+=2) {
3122                 Body labelBody = bodies().get(i);
3123                 if (labelBody.blocks().size() != 1) {
3124                     continue; // we skip, for now
3125                 }
3126                 Op terminatingOp = bodies().get(i).entryBlock().terminatingOp();
3127                 //@@@ when op pattern matching is ready, we can use it
3128                 if (terminatingOp instanceof CoreOp.YieldOp yieldOp &&
3129                         yieldOp.yieldValue() instanceof Op.Result opr &&
3130                         opr.op() instanceof InvokeOp invokeOp &&
3131                         invokeOp.invokeDescriptor().equals(MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class)) &&
3132                         invokeOp.operands().stream().anyMatch(o -> o instanceof Op.Result r && r.op() instanceof ConstantOp cop && cop.value() == null)) {
3133                     return true;
3134                 }
3135             }
3136             return false;
3137         }
3138     }
3139 
3140     /**
3141      * The switch expression operation, that can model Java language switch expressions.
3142      */
3143     @OpDeclaration(SwitchExpressionOp.NAME)
3144     public static final class SwitchExpressionOp extends JavaSwitchOp
3145             implements JavaExpression {
3146         static final String NAME = "java.switch.expression";
3147 
3148         final TypeElement resultType;
3149 
3150         SwitchExpressionOp(ExternalizedOp def) {
3151             this(def.resultType(), def.operands().get(0), def.bodyDefinitions());
3152         }
3153 
3154         SwitchExpressionOp(SwitchExpressionOp that, CodeContext cc, CodeTransformer ot) {
3155             super(that, cc, ot);
3156 
3157             this.resultType = that.resultType;
3158         }
3159 
3160         @Override
3161         public SwitchExpressionOp transform(CodeContext cc, CodeTransformer ot) {
3162             return new SwitchExpressionOp(this, cc, ot);
3163         }
3164 
3165         SwitchExpressionOp(TypeElement resultType, Value target, List<Body.Builder> bodyCs) {
3166             super(target, bodyCs);
3167 
3168             this.resultType = resultType == null ? bodies.get(1).yieldType() : resultType;
3169         }
3170 
3171         @Override
3172         public TypeElement resultType() {
3173             return resultType;
3174         }
3175     }
3176 
3177     /**
3178      * The switch statement operation, that can model Java language switch statement.
3179      */
3180     @OpDeclaration(SwitchStatementOp.NAME)
3181     public static final class SwitchStatementOp extends JavaSwitchOp
3182             implements JavaStatement {
3183         static final String NAME = "java.switch.statement";
3184 
3185         SwitchStatementOp(ExternalizedOp def) {
3186             this(def.operands().get(0), def.bodyDefinitions());
3187         }
3188 
3189         SwitchStatementOp(SwitchStatementOp that, CodeContext cc, CodeTransformer ot) {
3190             super(that, cc, ot);
3191         }
3192 
3193         @Override
3194         public SwitchStatementOp transform(CodeContext cc, CodeTransformer ot) {
3195             return new SwitchStatementOp(this, cc, ot);
3196         }
3197 
3198         SwitchStatementOp(Value target, List<Body.Builder> bodyCs) {
3199             super(target, bodyCs);
3200         }
3201 
3202         @Override
3203         public TypeElement resultType() {
3204             return VOID;
3205         }
3206     }
3207 
3208     /**
3209      * The switch fall-through operation, that can model fall-through to the next statement in the switch block after
3210      * the last statement of the current switch label.
3211      */
3212     @OpDeclaration(SwitchFallthroughOp.NAME)
3213     public static final class SwitchFallthroughOp extends JavaOp
3214             implements Op.BodyTerminating, Op.Lowerable {
3215         static final String NAME = "java.switch.fallthrough";
3216 
3217         SwitchFallthroughOp(ExternalizedOp def) {
3218             this();
3219         }
3220 
3221         SwitchFallthroughOp(SwitchFallthroughOp that, CodeContext cc) {
3222             super(that, cc);
3223         }
3224 
3225         @Override
3226         public SwitchFallthroughOp transform(CodeContext cc, CodeTransformer ot) {
3227             return new SwitchFallthroughOp(this, cc);
3228         }
3229 
3230         SwitchFallthroughOp() {
3231             super(List.of());
3232         }
3233 
3234         @Override
3235         public TypeElement resultType() {
3236             return VOID;
3237         }
3238 
3239         @Override
3240         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
3241             return lower(b, BranchTarget::continueBlock);
3242         }
3243 
3244         Block.Builder lower(Block.Builder b, Function<BranchTarget, Block.Builder> f) {
3245             BranchTarget t = BranchTarget.getBranchTarget(b.context(), ancestorBody());
3246             if (t != null) {
3247                 b.op(branch(f.apply(t).successor()));
3248             } else {
3249                 throw new IllegalStateException("No branch target for operation: " + this);
3250             }
3251             return b;
3252         }
3253     }
3254 
3255     /**
3256      * The for operation, that can model a Java language for statement.
3257      */
3258     @OpDeclaration(ForOp.NAME)
3259     public static final class ForOp extends JavaOp
3260             implements Op.Loop, Op.Lowerable, JavaStatement {
3261 
3262         /**
3263          * Builder for the initialization phase of a for-loop.
3264          */
3265         public static final class InitBuilder {
3266             final Body.Builder ancestorBody;
3267             final List<? extends TypeElement> initTypes;
3268 
3269             InitBuilder(Body.Builder ancestorBody,
3270                         List<? extends TypeElement> initTypes) {
3271                 this.ancestorBody = ancestorBody;
3272                 this.initTypes = initTypes.stream().map(CoreType::varType).toList();
3273             }
3274 
3275             /**
3276              * Builds the initialization step for a for-loop.
3277              * @param c a consumer to model the code initializing the loop variables
3278              * @return a conditional builder for the loop condition
3279              */
3280             public ForOp.CondBuilder init(Consumer<Block.Builder> c) {
3281                 Body.Builder init = Body.Builder.of(ancestorBody,
3282                         CoreType.functionType(CoreType.tupleType(initTypes)));
3283                 c.accept(init.entryBlock());
3284 
3285                 return new CondBuilder(ancestorBody, initTypes, init);
3286             }
3287         }
3288 
3289         /**
3290          * Builder for the condition (predicate) phase of a for-loop.
3291          */
3292         public static final class CondBuilder {
3293             final Body.Builder ancestorBody;
3294             final List<? extends TypeElement> initTypes;
3295             final Body.Builder init;
3296 
3297             CondBuilder(Body.Builder ancestorBody,
3298                                List<? extends TypeElement> initTypes,
3299                                Body.Builder init) {
3300                 this.ancestorBody = ancestorBody;
3301                 this.initTypes = initTypes;
3302                 this.init = init;
3303             }
3304 
3305             /**
3306              * Builds the predicate body for the loop.
3307              * @param c a consumer to model the for-loop condition
3308              * @return an update builder for building the update phase
3309              */
3310             public ForOp.UpdateBuilder cond(Consumer<Block.Builder> c) {
3311                 Body.Builder cond = Body.Builder.of(ancestorBody,
3312                         CoreType.functionType(BOOLEAN, initTypes));
3313                 c.accept(cond.entryBlock());
3314 
3315                 return new UpdateBuilder(ancestorBody, initTypes, init, cond);
3316             }
3317         }
3318 
3319         /**
3320          * Builder for the update phase of a for-loop (executed after each iteration).
3321          */
3322         public static final class UpdateBuilder {
3323             final Body.Builder ancestorBody;
3324             final List<? extends TypeElement> initTypes;
3325             final Body.Builder init;
3326             final Body.Builder cond;
3327 
3328             UpdateBuilder(Body.Builder ancestorBody,
3329                                  List<? extends TypeElement> initTypes,
3330                                  Body.Builder init, Body.Builder cond) {
3331                 this.ancestorBody = ancestorBody;
3332                 this.initTypes = initTypes;
3333                 this.init = init;
3334                 this.cond = cond;
3335             }
3336 
3337             /**
3338              * Builds the update logic to be run after each for-loop iteration.
3339              * @param c a consumer for the update logic
3340              * @return a body builder for constructing the loop body
3341              */
3342             public ForOp.BodyBuilder cond(Consumer<Block.Builder> c) {
3343                 Body.Builder update = Body.Builder.of(ancestorBody,
3344                         CoreType.functionType(VOID, initTypes));
3345                 c.accept(update.entryBlock());
3346 
3347                 return new BodyBuilder(ancestorBody, initTypes, init, cond, update);
3348             }
3349         }
3350 
3351         /**
3352          * Builder for the body (main logic) portion of a for-loop.
3353          */
3354         public static final class BodyBuilder {
3355             final Body.Builder ancestorBody;
3356             final List<? extends TypeElement> initTypes;
3357             final Body.Builder init;
3358             final Body.Builder cond;
3359             final Body.Builder update;
3360 
3361             BodyBuilder(Body.Builder ancestorBody,
3362                                List<? extends TypeElement> initTypes,
3363                                Body.Builder init, Body.Builder cond, Body.Builder update) {
3364                 this.ancestorBody = ancestorBody;
3365                 this.initTypes = initTypes;
3366                 this.init = init;
3367                 this.cond = cond;
3368                 this.update = update;
3369             }
3370 
3371             /**
3372              * Builds the loop body for the for-loop.
3373              * @param c a consumer for the main logic to run in the for-loop
3374              * @return the completed for-loop operation
3375              */
3376             public ForOp body(Consumer<Block.Builder> c) {
3377                 Body.Builder body = Body.Builder.of(ancestorBody,
3378                         CoreType.functionType(VOID, initTypes));
3379                 c.accept(body.entryBlock());
3380 
3381                 return new ForOp(init, cond, update, body);
3382             }
3383         }
3384 
3385         static final String NAME = "java.for";
3386 
3387         final Body init;
3388         final Body cond;
3389         final Body update;
3390         final Body body;
3391 
3392         ForOp(ExternalizedOp def) {
3393             this(def.bodyDefinitions().get(0),
3394                     def.bodyDefinitions().get(1),
3395                     def.bodyDefinitions().get(2),
3396                     def.bodyDefinitions().get(3));
3397         }
3398 
3399         ForOp(ForOp that, CodeContext cc, CodeTransformer ot) {
3400             super(that, cc);
3401 
3402             this.init = that.init.transform(cc, ot).build(this);
3403             this.cond = that.cond.transform(cc, ot).build(this);
3404             this.update = that.update.transform(cc, ot).build(this);
3405             this.body = that.body.transform(cc, ot).build(this);
3406         }
3407 
3408         @Override
3409         public ForOp transform(CodeContext cc, CodeTransformer ot) {
3410             return new ForOp(this, cc, ot);
3411         }
3412 
3413         ForOp(Body.Builder initC,
3414               Body.Builder condC,
3415               Body.Builder updateC,
3416               Body.Builder bodyC) {
3417             super(List.of());
3418 
3419             this.init = initC.build(this);
3420 
3421             this.cond = condC.build(this);
3422 
3423             this.update = updateC.build(this);
3424             if (!update.bodyType().returnType().equals(VOID)) {
3425                 throw new IllegalArgumentException("Update should return void: " + update.bodyType());
3426             }
3427 
3428             this.body = bodyC.build(this);
3429             if (!body.bodyType().returnType().equals(VOID)) {
3430                 throw new IllegalArgumentException("Body should return void: " + body.bodyType());
3431             }
3432         }
3433 
3434         @Override
3435         public List<Body> bodies() {
3436             return List.of(init, cond, update, body);
3437         }
3438 
3439         /**
3440          * {@return the initialization body}
3441          */
3442         public Body init() {
3443             return init;
3444         }
3445 
3446         /**
3447          * {@return the loop condition (predicate) body}
3448          */
3449         public Body cond() {
3450             return cond;
3451         }
3452 
3453         /**
3454          * {@return the update body}
3455          */
3456         public Body update() {
3457             return update;
3458         }
3459 
3460         @Override
3461         public Body loopBody() {
3462             return body;
3463         }
3464 
3465         @Override
3466         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
3467             Block.Builder header = b.block();
3468             Block.Builder body = b.block();
3469             Block.Builder update = b.block();
3470             Block.Builder exit = b.block();
3471 
3472             List<Value> initValues = new ArrayList<>();
3473             // @@@ Init body has one yield operation yielding
3474             //  void, a single variable, or a tuple of one or more variables
3475             b.body(init, List.of(), andThenLowering(opT, (block, op) -> switch (op) {
3476                 case TupleOp _ -> {
3477                     // Drop Tuple if a yielded
3478                     boolean isResult = op.result().uses().size() == 1 &&
3479                             op.result().uses().stream().allMatch(r -> r.op() instanceof CoreOp.YieldOp);
3480                     if (!isResult) {
3481                         block.op(op);
3482                     }
3483                     yield  block;
3484                 }
3485                 case CoreOp.YieldOp yop -> {
3486                     if (yop.yieldValue() == null) {
3487                         block.op(branch(header.successor()));
3488                         yield block;
3489                     } else if (yop.yieldValue() instanceof Result or) {
3490                         if (or.op() instanceof TupleOp top) {
3491                             initValues.addAll(block.context().getValues(top.operands()));
3492                         } else {
3493                             initValues.addAll(block.context().getValues(yop.operands()));
3494                         }
3495                         block.op(branch(header.successor()));
3496                         yield block;
3497                     }
3498 
3499                     throw new IllegalStateException("Bad yield operation");
3500                 }
3501                 default -> null;
3502             }));
3503 
3504             header.body(cond, initValues, andThenLowering(opT, (block, op) -> {
3505                 if (op instanceof CoreOp.YieldOp yo) {
3506                     block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
3507                             body.successor(), exit.successor()));
3508                     return block;
3509                 } else {
3510                     return null;
3511                 }
3512             }));
3513 
3514             BranchTarget.setBranchTarget(b.context(), this, exit, update);
3515 
3516             body.body(this.body, initValues, andThenLowering(opT, (_, _) -> null));
3517 
3518             update.body(this.update, initValues, andThenLowering(opT, (block, op) -> {
3519                 if (op instanceof CoreOp.YieldOp) {
3520                     block.op(branch(header.successor()));
3521                     return block;
3522                 } else {
3523                     return null;
3524                 }
3525             }));
3526 
3527             return exit;
3528         }
3529 
3530         @Override
3531         public TypeElement resultType() {
3532             return VOID;
3533         }
3534     }
3535 
3536     /**
3537      * The enhanced for operation, that can model a Java language enhanced for statement.
3538      */
3539     @OpDeclaration(EnhancedForOp.NAME)
3540     public static final class EnhancedForOp extends JavaOp
3541             implements Op.Loop, Op.Lowerable, JavaStatement {
3542 
3543         /**
3544          * Builder for the iterable expression in an enhanced-for loop.
3545          */
3546         public static final class ExpressionBuilder {
3547             final Body.Builder ancestorBody;
3548             final TypeElement iterableType;
3549             final TypeElement elementType;
3550 
3551             ExpressionBuilder(Body.Builder ancestorBody,
3552                               TypeElement iterableType, TypeElement elementType) {
3553                 this.ancestorBody = ancestorBody;
3554                 this.iterableType = iterableType;
3555                 this.elementType = elementType;
3556             }
3557 
3558             /**
3559              * Builds the iterable expression for an enhanced-for loop.
3560              * @param c a consumer that populates the block modelling the iterable expression
3561              * @return a definition builder, for specifying the variable definition in the loop
3562              */
3563             public DefinitionBuilder expression(Consumer<Block.Builder> c) {
3564                 Body.Builder expression = Body.Builder.of(ancestorBody,
3565                         CoreType.functionType(iterableType));
3566                 c.accept(expression.entryBlock());
3567 
3568                 return new DefinitionBuilder(ancestorBody, elementType, expression);
3569             }
3570         }
3571 
3572         /**
3573          * Builder for the iteration variable definition in an enhanced-for loop.
3574          */
3575         public static final class DefinitionBuilder {
3576             final Body.Builder ancestorBody;
3577             final TypeElement elementType;
3578             final Body.Builder expression;
3579 
3580             DefinitionBuilder(Body.Builder ancestorBody,
3581                               TypeElement elementType, Body.Builder expression) {
3582                 this.ancestorBody = ancestorBody;
3583                 this.elementType = elementType;
3584                 this.expression = expression;
3585             }
3586 
3587             /**
3588              * Defines the loop variable for the enhanced-for operation, using a type
3589              * derived from the iterable expression.
3590              * @param c a consumer that populates the block defining the loop variable
3591              * @return a body builder for the loop body
3592              */
3593             public BodyBuilder definition(Consumer<Block.Builder> c) {
3594                 return definition(elementType, c);
3595             }
3596 
3597             /**
3598              * Defines the loop variable for the enhanced-for operation with the provided type.
3599              * @param bodyElementType the type to use for the variable in the loop body
3600              * @param c a consumer that populates the block defining the loop variable
3601              * @return a body builder for the loop body
3602              */
3603             public BodyBuilder definition(TypeElement bodyElementType, Consumer<Block.Builder> c) {
3604                 Body.Builder definition = Body.Builder.of(ancestorBody,
3605                         CoreType.functionType(bodyElementType, elementType));
3606                 c.accept(definition.entryBlock());
3607 
3608                 return new BodyBuilder(ancestorBody, elementType, expression, definition);
3609             }
3610         }
3611 
3612         /**
3613          * Builder for an enhanced-for operation body.
3614          */
3615         public static final class BodyBuilder {
3616             final Body.Builder ancestorBody;
3617             final TypeElement elementType;
3618             final Body.Builder expression;
3619             final Body.Builder definition;
3620 
3621             BodyBuilder(Body.Builder ancestorBody,
3622                         TypeElement elementType, Body.Builder expression, Body.Builder definition) {
3623                 this.ancestorBody = ancestorBody;
3624                 this.elementType = elementType;
3625                 this.expression = expression;
3626                 this.definition = definition;
3627             }
3628 
3629             /**
3630              * Completes the construction of the enhanced-for loop operation with the body obtained from the provided builder.
3631              * @param c a consumer that populates the block for the enhanced-for loop body
3632              * @return the completed enhanced-for operation
3633              */
3634             public EnhancedForOp body(Consumer<Block.Builder> c) {
3635                 Body.Builder body = Body.Builder.of(ancestorBody,
3636                         CoreType.functionType(VOID, elementType));
3637                 c.accept(body.entryBlock());
3638 
3639                 return new EnhancedForOp(expression, definition, body);
3640             }
3641         }
3642 
3643         static final String NAME = "java.enhancedFor";
3644 
3645         final Body expression;
3646         final Body init;
3647         final Body body;
3648 
3649         EnhancedForOp(ExternalizedOp def) {
3650             this(def.bodyDefinitions().get(0),
3651                     def.bodyDefinitions().get(1),
3652                     def.bodyDefinitions().get(2));
3653         }
3654 
3655         EnhancedForOp(EnhancedForOp that, CodeContext cc, CodeTransformer ot) {
3656             super(that, cc);
3657 
3658             this.expression = that.expression.transform(cc, ot).build(this);
3659             this.init = that.init.transform(cc, ot).build(this);
3660             this.body = that.body.transform(cc, ot).build(this);
3661         }
3662 
3663         @Override
3664         public EnhancedForOp transform(CodeContext cc, CodeTransformer ot) {
3665             return new EnhancedForOp(this, cc, ot);
3666         }
3667 
3668         EnhancedForOp(Body.Builder expressionC, Body.Builder initC, Body.Builder bodyC) {
3669             super(List.of());
3670 
3671             this.expression = expressionC.build(this);
3672             if (expression.bodyType().returnType().equals(VOID)) {
3673                 throw new IllegalArgumentException("Expression should return non-void value: " + expression.bodyType());
3674             }
3675             if (!expression.bodyType().parameterTypes().isEmpty()) {
3676                 throw new IllegalArgumentException("Expression should have zero parameters: " + expression.bodyType());
3677             }
3678 
3679             this.init = initC.build(this);
3680             if (init.bodyType().returnType().equals(VOID)) {
3681                 throw new IllegalArgumentException("Initialization should return non-void value: " + init.bodyType());
3682             }
3683             if (init.bodyType().parameterTypes().size() != 1) {
3684                 throw new IllegalArgumentException("Initialization should have one parameter: " + init.bodyType());
3685             }
3686 
3687             this.body = bodyC.build(this);
3688             if (!body.bodyType().returnType().equals(VOID)) {
3689                 throw new IllegalArgumentException("Body should return void: " + body.bodyType());
3690             }
3691             if (body.bodyType().parameterTypes().size() != 1) {
3692                 throw new IllegalArgumentException("Body should have one parameter: " + body.bodyType());
3693             }
3694         }
3695 
3696         @Override
3697         public List<Body> bodies() {
3698             return List.of(expression, init, body);
3699         }
3700 
3701         /**
3702          * {@return the expression body}
3703          */
3704         public Body expression() {
3705             return expression;
3706         }
3707 
3708         /**
3709          * {@return the initialization body}
3710          */
3711         public Body initialization() {
3712             return init;
3713         }
3714 
3715         @Override
3716         public Body loopBody() {
3717             return body;
3718         }
3719 
3720         static final MethodRef ITERABLE_ITERATOR = MethodRef.method(Iterable.class, "iterator", Iterator.class);
3721         static final MethodRef ITERATOR_HAS_NEXT = MethodRef.method(Iterator.class, "hasNext", boolean.class);
3722         static final MethodRef ITERATOR_NEXT = MethodRef.method(Iterator.class, "next", Object.class);
3723 
3724         @Override
3725         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
3726             JavaType elementType = (JavaType) init.entryBlock().parameters().get(0).type();
3727             boolean isArray = expression.bodyType().returnType() instanceof ArrayType;
3728 
3729             Block.Builder preHeader = b.block(expression.bodyType().returnType());
3730             Block.Builder header = b.block(isArray ? List.of(INT) : List.of());
3731             Block.Builder init = b.block();
3732             Block.Builder body = b.block();
3733             Block.Builder exit = b.block();
3734 
3735             b.body(expression, List.of(), andThenLowering(opT, (block, op) -> {
3736                 if (op instanceof CoreOp.YieldOp yop) {
3737                     Value loopSource = block.context().getValue(yop.yieldValue());
3738                     block.op(branch(preHeader.successor(loopSource)));
3739                     return block;
3740                 } else {
3741                     return null;
3742                 }
3743             }));
3744 
3745             if (isArray) {
3746                 Value array = preHeader.parameters().get(0);
3747                 Value arrayLength = preHeader.op(arrayLength(array));
3748                 Value i = preHeader.op(constant(INT, 0));
3749                 preHeader.op(branch(header.successor(i)));
3750 
3751                 i = header.parameters().get(0);
3752                 Value p = header.op(lt(i, arrayLength));
3753                 header.op(conditionalBranch(p, init.successor(), exit.successor()));
3754 
3755                 Value e = init.op(arrayLoadOp(array, i));
3756                 List<Value> initValues = new ArrayList<>();
3757                 init.body(this.init, List.of(e), andThenLowering(opT, (block, op) -> {
3758                     if (op instanceof CoreOp.YieldOp yop) {
3759                         initValues.addAll(block.context().getValues(yop.operands()));
3760                         block.op(branch(body.successor()));
3761                         return block;
3762                     } else {
3763                         return null;
3764                     }
3765                 }));
3766 
3767                 Block.Builder update = b.block();
3768                 BranchTarget.setBranchTarget(b.context(), this, exit, update);
3769 
3770                 body.body(this.body, initValues, andThenLowering(opT, (_, _) -> null));
3771 
3772                 i = update.op(add(i, update.op(constant(INT, 1))));
3773                 update.op(branch(header.successor(i)));
3774             } else {
3775                 JavaType iterable = parameterized(type(Iterator.class), elementType);
3776                 Value iterator = preHeader.op(invoke(iterable, ITERABLE_ITERATOR, preHeader.parameters().get(0)));
3777                 preHeader.op(branch(header.successor()));
3778 
3779                 Value p = header.op(invoke(ITERATOR_HAS_NEXT, iterator));
3780                 header.op(conditionalBranch(p, init.successor(), exit.successor()));
3781 
3782                 Value e = init.op(invoke(elementType, ITERATOR_NEXT, iterator));
3783                 List<Value> initValues = new ArrayList<>();
3784                 init.body(this.init, List.of(e), andThenLowering(opT, (block, op) -> {
3785                     if (op instanceof CoreOp.YieldOp yop) {
3786                         initValues.addAll(block.context().getValues(yop.operands()));
3787                         block.op(branch(body.successor()));
3788                         return block;
3789                     } else {
3790                         return null;
3791                     }
3792                 }));
3793 
3794                 BranchTarget.setBranchTarget(b.context(), this, exit, header);
3795 
3796                 body.body(this.body, initValues, andThenLowering(opT, (_, _) -> null));
3797             }
3798 
3799             return exit;
3800         }
3801 
3802         @Override
3803         public TypeElement resultType() {
3804             return VOID;
3805         }
3806     }
3807 
3808     /**
3809      * The while operation, that can model a Java language while statement.
3810      */
3811     @OpDeclaration(WhileOp.NAME)
3812     public static final class WhileOp extends JavaOp
3813             implements Op.Loop, Op.Lowerable, JavaStatement {
3814 
3815         /**
3816          * A builder for specifying the predicate (loop condition) of a while loop operation.
3817          */
3818         public static class PredicateBuilder {
3819             final Body.Builder ancestorBody;
3820 
3821             PredicateBuilder(Body.Builder ancestorBody) {
3822                 this.ancestorBody = ancestorBody;
3823             }
3824 
3825             /**
3826              * Builds the predicate (loop condition) body for the while operation.
3827              *
3828              * @param c a consumer to populate the block for the predicate
3829              * @return a builder for the main loop body
3830              */
3831             public WhileOp.BodyBuilder predicate(Consumer<Block.Builder> c) {
3832                 Body.Builder body = Body.Builder.of(ancestorBody, CoreType.functionType(BOOLEAN));
3833                 c.accept(body.entryBlock());
3834 
3835                 return new WhileOp.BodyBuilder(ancestorBody, body);
3836             }
3837         }
3838 
3839         /**
3840          * A builder for specifying the main body of a while loop operation.
3841          */
3842         public static class BodyBuilder {
3843             final Body.Builder ancestorBody;
3844             private final Body.Builder predicate;
3845 
3846             BodyBuilder(Body.Builder ancestorBody, Body.Builder predicate) {
3847                 this.ancestorBody = ancestorBody;
3848                 this.predicate = predicate;
3849             }
3850 
3851             /**
3852              * Builds the main logic body for the while loop operation.
3853              *
3854              * @param c a consumer to populate the block for the loop body
3855              * @return the completed while loop operation
3856              */
3857             public WhileOp body(Consumer<Block.Builder> c) {
3858                 Body.Builder body = Body.Builder.of(ancestorBody, CoreType.FUNCTION_TYPE_VOID);
3859                 c.accept(body.entryBlock());
3860 
3861                 return new WhileOp(List.of(predicate, body));
3862             }
3863         }
3864 
3865         private static final String NAME = "java.while";
3866 
3867         private final List<Body> bodies;
3868 
3869         WhileOp(ExternalizedOp def) {
3870             this(def.bodyDefinitions());
3871         }
3872 
3873         WhileOp(List<Body.Builder> bodyCs) {
3874             super(List.of());
3875 
3876             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
3877         }
3878 
3879         WhileOp(Body.Builder predicate, Body.Builder body) {
3880             super(List.of());
3881 
3882             Objects.requireNonNull(body);
3883 
3884             this.bodies = Stream.of(predicate, body).filter(Objects::nonNull)
3885                     .map(bc -> bc.build(this)).toList();
3886 
3887             // @@@ This will change with pattern bindings
3888             if (!bodies.get(0).bodyType().equals(CoreType.functionType(BOOLEAN))) {
3889                 throw new IllegalArgumentException(
3890                         "Predicate body descriptor should be " + CoreType.functionType(BOOLEAN) +
3891                                 " but is " + bodies.get(0).bodyType());
3892             }
3893             if (!bodies.get(1).bodyType().equals(CoreType.FUNCTION_TYPE_VOID)) {
3894                 throw new IllegalArgumentException(
3895                         "Body descriptor should be " + CoreType.functionType(VOID) +
3896                                 " but is " + bodies.get(1).bodyType());
3897             }
3898         }
3899 
3900         WhileOp(WhileOp that, CodeContext cc, CodeTransformer ot) {
3901             super(that, cc);
3902 
3903             this.bodies = that.bodies.stream()
3904                     .map(b -> b.transform(cc, ot).build(this)).toList();
3905         }
3906 
3907         @Override
3908         public WhileOp transform(CodeContext cc, CodeTransformer ot) {
3909             return new WhileOp(this, cc, ot);
3910         }
3911 
3912         @Override
3913         public List<Body> bodies() {
3914             return bodies;
3915         }
3916 
3917         /**
3918          * {@return the loop condition body}
3919          */
3920         public Body predicateBody() {
3921             return bodies.get(0);
3922         }
3923 
3924         @Override
3925         public Body loopBody() {
3926             return bodies.get(1);
3927         }
3928 
3929         @Override
3930         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
3931             Block.Builder header = b.block();
3932             Block.Builder body = b.block();
3933             Block.Builder exit = b.block();
3934 
3935             b.op(branch(header.successor()));
3936 
3937             header.body(predicateBody(), List.of(), andThenLowering(opT, (block, op) -> {
3938                 if (op instanceof CoreOp.YieldOp yo) {
3939                     block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
3940                             body.successor(), exit.successor()));
3941                     return block;
3942                 } else {
3943                     return null;
3944                 }
3945             }));
3946 
3947             BranchTarget.setBranchTarget(b.context(), this, exit, header);
3948 
3949             body.body(loopBody(), List.of(), andThenLowering(opT, (_, _) -> null));
3950 
3951             return exit;
3952         }
3953 
3954         @Override
3955         public TypeElement resultType() {
3956             return VOID;
3957         }
3958     }
3959 
3960     /**
3961      * The do-while operation, that can model a Java language do statement.
3962      */
3963     // @@@ Unify JavaDoWhileOp and JavaWhileOp with common abstract superclass
3964     @OpDeclaration(DoWhileOp.NAME)
3965     public static final class DoWhileOp extends JavaOp
3966             implements Op.Loop, Op.Lowerable, JavaStatement {
3967 
3968         /**
3969          * Builder for a do-while predicate.
3970          */
3971         public static class PredicateBuilder {
3972             final Body.Builder ancestorBody;
3973             private final Body.Builder body;
3974 
3975             PredicateBuilder(Body.Builder ancestorBody, Body.Builder body) {
3976                 this.ancestorBody = ancestorBody;
3977                 this.body = body;
3978             }
3979 
3980             /**
3981              * Completes the construction of a do-while operation with the predicate obtained from the provided builder.
3982              * @param c a consumer that populates the block modelling the do-while predicate
3983              * @return a completed do-while operation with the provided predicate
3984              */
3985             public DoWhileOp predicate(Consumer<Block.Builder> c) {
3986                 Body.Builder predicate = Body.Builder.of(ancestorBody, CoreType.functionType(BOOLEAN));
3987                 c.accept(predicate.entryBlock());
3988 
3989                 return new DoWhileOp(List.of(body, predicate));
3990             }
3991         }
3992 
3993         /**
3994          * Builder for a do-while body.
3995          */
3996         public static class BodyBuilder {
3997             final Body.Builder ancestorBody;
3998 
3999             BodyBuilder(Body.Builder ancestorBody) {
4000                 this.ancestorBody = ancestorBody;
4001             }
4002 
4003             /**
4004              * Builds the body of a do-while loop.
4005              * @param c a consumer that populates the block modeling the body of the loop
4006              * @return a predicate builder for configuring this do-while operation
4007              */
4008             public DoWhileOp.PredicateBuilder body(Consumer<Block.Builder> c) {
4009                 Body.Builder body = Body.Builder.of(ancestorBody, CoreType.FUNCTION_TYPE_VOID);
4010                 c.accept(body.entryBlock());
4011 
4012                 return new DoWhileOp.PredicateBuilder(ancestorBody, body);
4013             }
4014         }
4015 
4016         private static final String NAME = "java.do.while";
4017 
4018         private final List<Body> bodies;
4019 
4020         DoWhileOp(ExternalizedOp def) {
4021             this(def.bodyDefinitions());
4022         }
4023 
4024         DoWhileOp(List<Body.Builder> bodyCs) {
4025             super(List.of());
4026 
4027             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
4028         }
4029 
4030         DoWhileOp(Body.Builder body, Body.Builder predicate) {
4031             super(List.of());
4032 
4033             Objects.requireNonNull(body);
4034 
4035             this.bodies = Stream.of(body, predicate).filter(Objects::nonNull)
4036                     .map(bc -> bc.build(this)).toList();
4037 
4038             if (!bodies.get(0).bodyType().equals(CoreType.FUNCTION_TYPE_VOID)) {
4039                 throw new IllegalArgumentException(
4040                         "Body descriptor should be " + CoreType.functionType(VOID) +
4041                                 " but is " + bodies.get(1).bodyType());
4042             }
4043             if (!bodies.get(1).bodyType().equals(CoreType.functionType(BOOLEAN))) {
4044                 throw new IllegalArgumentException(
4045                         "Predicate body descriptor should be " + CoreType.functionType(BOOLEAN) +
4046                                 " but is " + bodies.get(0).bodyType());
4047             }
4048         }
4049 
4050         DoWhileOp(DoWhileOp that, CodeContext cc, CodeTransformer ot) {
4051             super(that, cc);
4052 
4053             this.bodies = that.bodies.stream()
4054                     .map(b -> b.transform(cc, ot).build(this)).toList();
4055         }
4056 
4057         @Override
4058         public DoWhileOp transform(CodeContext cc, CodeTransformer ot) {
4059             return new DoWhileOp(this, cc, ot);
4060         }
4061 
4062         @Override
4063         public List<Body> bodies() {
4064             return bodies;
4065         }
4066 
4067         /**
4068          * {@return the predicate body for the do-while operation}
4069          */
4070         public Body predicateBody() {
4071             return bodies.get(1);
4072         }
4073 
4074         @Override
4075         public Body loopBody() {
4076             return bodies.get(0);
4077         }
4078 
4079         @Override
4080         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
4081             Block.Builder body = b.block();
4082             Block.Builder header = b.block();
4083             Block.Builder exit = b.block();
4084 
4085             b.op(branch(body.successor()));
4086 
4087             BranchTarget.setBranchTarget(b.context(), this, exit, header);
4088 
4089             body.body(loopBody(), List.of(), andThenLowering(opT, (_, _) -> null));
4090 
4091             header.body(predicateBody(), List.of(), andThenLowering(opT, (block, op) -> {
4092                 if (op instanceof CoreOp.YieldOp yo) {
4093                     block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
4094                             body.successor(), exit.successor()));
4095                     return block;
4096                 } else {
4097                     return null;
4098                 }
4099             }));
4100 
4101             return exit;
4102         }
4103 
4104         @Override
4105         public TypeElement resultType() {
4106             return VOID;
4107         }
4108     }
4109 
4110     /**
4111      * The conditional-and-or operation, that can model Java language condition-or or conditional-and expressions.
4112      */
4113     public sealed static abstract class JavaConditionalOp extends JavaOp
4114             implements Op.Nested, Op.Lowerable, JavaExpression {
4115         final List<Body> bodies;
4116 
4117         JavaConditionalOp(JavaConditionalOp that, CodeContext cc, CodeTransformer ot) {
4118             super(that, cc);
4119 
4120             // Copy body
4121             this.bodies = that.bodies.stream().map(b -> b.transform(cc, ot).build(this)).toList();
4122         }
4123 
4124         JavaConditionalOp(List<Body.Builder> bodyCs) {
4125             super(List.of());
4126 
4127             if (bodyCs.isEmpty()) {
4128                 throw new IllegalArgumentException();
4129             }
4130 
4131             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
4132             for (Body b : bodies) {
4133                 if (!b.bodyType().equals(CoreType.functionType(BOOLEAN))) {
4134                     throw new IllegalArgumentException("Body conditional body descriptor: " + b.bodyType());
4135                 }
4136             }
4137         }
4138 
4139         @Override
4140         public List<Body> bodies() {
4141             return bodies;
4142         }
4143 
4144         static Block.Builder lower(Block.Builder startBlock, CodeTransformer opT, JavaConditionalOp cop) {
4145             List<Body> bodies = cop.bodies();
4146 
4147             Block.Builder exit = startBlock.block();
4148             TypeElement oprType = cop.result().type();
4149             Block.Parameter arg = exit.parameter(oprType);
4150             startBlock.context().mapValue(cop.result(), arg);
4151 
4152             // Transform bodies in reverse order
4153             // This makes available the blocks to be referenced as successors in prior blocks
4154 
4155             Block.Builder pred = null;
4156             for (int i = bodies.size() - 1; i >= 0; i--) {
4157                 BiFunction<Block.Builder, Op, Block.Builder> opt;
4158                 if (i == bodies.size() - 1) {
4159                     opt = lowering(opT, (block, op) -> {
4160                         if (op instanceof CoreOp.YieldOp yop) {
4161                             Value p = block.context().getValue(yop.yieldValue());
4162                             block.op(branch(exit.successor(p)));
4163                             return block;
4164                         } else {
4165                             return null;
4166                         }
4167                     });
4168                 } else {
4169                     Block.Builder nextPred = pred;
4170                     opt = lowering(opT, (block, op) -> {
4171                         if (op instanceof CoreOp.YieldOp yop) {
4172                             Value p = block.context().getValue(yop.yieldValue());
4173                             if (cop instanceof ConditionalAndOp) {
4174                                 block.op(conditionalBranch(p, nextPred.successor(), exit.successor(p)));
4175                             } else {
4176                                 block.op(conditionalBranch(p, exit.successor(p), nextPred.successor()));
4177                             }
4178                             return block;
4179                         } else {
4180                             return null;
4181                         }
4182                     });
4183                 }
4184 
4185                 Body fromPred = bodies.get(i);
4186                 if (i == 0) {
4187                     startBlock.body(fromPred, List.of(), opt::apply);
4188                 } else {
4189                     pred = startBlock.block(fromPred.bodyType().parameterTypes());
4190                     pred.body(fromPred, pred.parameters(), andThen(opT, opt));
4191                 }
4192             }
4193 
4194             return exit;
4195         }
4196 
4197         @Override
4198         public TypeElement resultType() {
4199             return BOOLEAN;
4200         }
4201     }
4202 
4203     /**
4204      * The conditional-and operation, that can model Java language conditional-and expressions.
4205      */
4206     @OpDeclaration(ConditionalAndOp.NAME)
4207     public static final class ConditionalAndOp extends JavaConditionalOp {
4208 
4209         /**
4210          * Builder for conditional-and operations.
4211          */
4212         public static class Builder {
4213             final Body.Builder ancestorBody;
4214             final List<Body.Builder> bodies;
4215 
4216             Builder(Body.Builder ancestorBody, Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
4217                 this.ancestorBody = ancestorBody;
4218                 this.bodies = new ArrayList<>();
4219                 and(lhs);
4220                 and(rhs);
4221             }
4222 
4223             /**
4224              * Adds a condition to this builder.
4225              * @param c the consumer populating the block modelling the additional condition
4226              * @return this builder
4227              */
4228             public Builder and(Consumer<Block.Builder> c) {
4229                 Body.Builder body = Body.Builder.of(ancestorBody, CoreType.functionType(BOOLEAN));
4230                 c.accept(body.entryBlock());
4231                 bodies.add(body);
4232 
4233                 return this;
4234             }
4235 
4236             /**
4237              * {@return the conditional-and operation from the accumulated conditions}
4238              */
4239             public ConditionalAndOp build() {
4240                 return new ConditionalAndOp(bodies);
4241             }
4242         }
4243 
4244         static final String NAME = "java.cand";
4245 
4246         ConditionalAndOp(ExternalizedOp def) {
4247             this(def.bodyDefinitions());
4248         }
4249 
4250         ConditionalAndOp(ConditionalAndOp that, CodeContext cc, CodeTransformer ot) {
4251             super(that, cc, ot);
4252         }
4253 
4254         @Override
4255         public ConditionalAndOp transform(CodeContext cc, CodeTransformer ot) {
4256             return new ConditionalAndOp(this, cc, ot);
4257         }
4258 
4259         ConditionalAndOp(List<Body.Builder> bodyCs) {
4260             super(bodyCs);
4261         }
4262 
4263         @Override
4264         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
4265             return lower(b, opT, this);
4266         }
4267     }
4268 
4269     /**
4270      * The conditional-or operation, that can model Java language conditional-or expressions.
4271      */
4272     @OpDeclaration(ConditionalOrOp.NAME)
4273     public static final class ConditionalOrOp extends JavaConditionalOp {
4274 
4275         /**
4276          * Builder for conditional-or operations.
4277          */
4278         public static class Builder {
4279             final Body.Builder ancestorBody;
4280             final List<Body.Builder> bodies;
4281 
4282             Builder(Body.Builder ancestorBody, Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
4283                 this.ancestorBody = ancestorBody;
4284                 this.bodies = new ArrayList<>();
4285                 or(lhs);
4286                 or(rhs);
4287             }
4288 
4289             /**
4290              * Adds a condition to this builder.
4291              * @param c the consumer populating the block modelling the additional condition
4292              * @return this builder
4293              */
4294             public Builder or(Consumer<Block.Builder> c) {
4295                 Body.Builder body = Body.Builder.of(ancestorBody, CoreType.functionType(BOOLEAN));
4296                 c.accept(body.entryBlock());
4297                 bodies.add(body);
4298 
4299                 return this;
4300             }
4301 
4302             /**
4303              * {@return the conditional-or operation from the accumulated conditions}
4304              */
4305             public ConditionalOrOp build() {
4306                 return new ConditionalOrOp(bodies);
4307             }
4308         }
4309 
4310         static final String NAME = "java.cor";
4311 
4312         ConditionalOrOp(ExternalizedOp def) {
4313             this(def.bodyDefinitions());
4314         }
4315 
4316         ConditionalOrOp(ConditionalOrOp that, CodeContext cc, CodeTransformer ot) {
4317             super(that, cc, ot);
4318         }
4319 
4320         @Override
4321         public ConditionalOrOp transform(CodeContext cc, CodeTransformer ot) {
4322             return new ConditionalOrOp(this, cc, ot);
4323         }
4324 
4325         ConditionalOrOp(List<Body.Builder> bodyCs) {
4326             super(bodyCs);
4327         }
4328 
4329         @Override
4330         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
4331             return lower(b, opT, this);
4332         }
4333     }
4334 
4335     /**
4336      * The conditional operation, that can model Java language conditional operator {@code ?} expressions.
4337      */
4338     @OpDeclaration(ConditionalExpressionOp.NAME)
4339     public static final class ConditionalExpressionOp extends JavaOp
4340             implements Op.Nested, Op.Lowerable, JavaExpression {
4341 
4342         static final String NAME = "java.cexpression";
4343 
4344         final TypeElement resultType;
4345         // {cond, truepart, falsepart}
4346         final List<Body> bodies;
4347 
4348         ConditionalExpressionOp(ExternalizedOp def) {
4349             if (!def.operands().isEmpty()) {
4350                 throw new IllegalStateException("Operation must have no operands");
4351             }
4352 
4353             this(def.resultType(), def.bodyDefinitions());
4354         }
4355 
4356         ConditionalExpressionOp(ConditionalExpressionOp that, CodeContext cc, CodeTransformer ot) {
4357             super(that, cc);
4358 
4359             // Copy body
4360             this.bodies = that.bodies.stream()
4361                     .map(b -> b.transform(cc, ot).build(this)).toList();
4362             this.resultType = that.resultType;
4363         }
4364 
4365         @Override
4366         public ConditionalExpressionOp transform(CodeContext cc, CodeTransformer ot) {
4367             return new ConditionalExpressionOp(this, cc, ot);
4368         }
4369 
4370         ConditionalExpressionOp(TypeElement expressionType, List<Body.Builder> bodyCs) {
4371             super(List.of());
4372 
4373             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
4374             // @@@ when expressionType is null, we assume truepart and falsepart have the same yieldType
4375             this.resultType = expressionType == null ? bodies.get(1).yieldType() : expressionType;
4376 
4377             if (bodies.size() < 3) {
4378                 throw new IllegalArgumentException("Incorrect number of bodies: " + bodies.size());
4379             }
4380 
4381             Body cond = bodies.get(0);
4382             if (!cond.bodyType().equals(CoreType.functionType(BOOLEAN))) {
4383                 throw new IllegalArgumentException("Illegal cond body descriptor: " + cond.bodyType());
4384             }
4385         }
4386 
4387         @Override
4388         public List<Body> bodies() {
4389             return bodies;
4390         }
4391 
4392         @Override
4393         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
4394             Block.Builder exit = b.block(resultType());
4395             exit.context().mapValue(result(), exit.parameters().get(0));
4396 
4397             BranchTarget.setBranchTarget(b.context(), this, exit, null);
4398 
4399             List<Block.Builder> builders = List.of(b.block(), b.block());
4400             b.body(bodies.get(0), List.of(), andThenLowering(opT, (block, op) -> {
4401                 if (op instanceof CoreOp.YieldOp yo) {
4402                     block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
4403                             builders.get(0).successor(), builders.get(1).successor()));
4404                     return block;
4405                 } else {
4406                     return null;
4407                 }
4408             }));
4409 
4410             for (int i = 0; i < 2; i++) {
4411                 builders.get(i).body(bodies.get(i + 1), List.of(), andThenLowering(opT, (block, op) -> {
4412                     if (op instanceof CoreOp.YieldOp yop) {
4413                         block.op(branch(exit.successor(block.context().getValue(yop.yieldValue()))));
4414                         return block;
4415                     } else {
4416                         return null;
4417                     }
4418                 }));
4419             }
4420 
4421             return exit;
4422         }
4423 
4424         @Override
4425         public TypeElement resultType() {
4426             return resultType;
4427         }
4428     }
4429 
4430     /**
4431      * The try operation, that can model Java language try statements.
4432      */
4433     @OpDeclaration(TryOp.NAME)
4434     public static final class TryOp extends JavaOp
4435             implements Op.Nested, Op.Lowerable, JavaStatement {
4436 
4437         /**
4438          * A builder for specifying the main body of a try operation.
4439          */
4440         public static final class BodyBuilder {
4441             final Body.Builder ancestorBody;
4442             final List<? extends TypeElement> resourceTypes;
4443             final Body.Builder resources;
4444 
4445             BodyBuilder(Body.Builder ancestorBody, List<? extends TypeElement> resourceTypes, Body.Builder resources) {
4446                 this.ancestorBody = ancestorBody;
4447                 this.resourceTypes = resourceTypes;
4448                 this.resources = resources;
4449             }
4450 
4451             /**
4452              * Builds the main body of the try operation.
4453              *
4454              * @param c a consumer to populate the main body of the try block
4455              * @return a builder for catch and finalizer clauses
4456              */
4457             public CatchBuilder body(Consumer<Block.Builder> c) {
4458                 Body.Builder body = Body.Builder.of(ancestorBody,
4459                         CoreType.functionType(VOID, resourceTypes));
4460                 c.accept(body.entryBlock());
4461 
4462                 return new CatchBuilder(ancestorBody, resources, body);
4463             }
4464         }
4465 
4466         /**
4467          * A builder for specifying the catch clauses and finalizer for a try operation.
4468          */
4469         public static final class CatchBuilder {
4470             final Body.Builder ancestorBody;
4471             final Body.Builder resources;
4472             final Body.Builder body;
4473             final List<Body.Builder> catchers;
4474 
4475             CatchBuilder(Body.Builder ancestorBody, Body.Builder resources, Body.Builder body) {
4476                 this.ancestorBody = ancestorBody;
4477                 this.resources = resources;
4478                 this.body = body;
4479                 this.catchers = new ArrayList<>();
4480             }
4481 
4482             // @@@ multi-catch
4483             /**
4484              * Adds a catch clause for handling exceptions of a specific type.
4485              *
4486              * @param exceptionType the type of exception handled
4487              * @param c a consumer to populate the catch body
4488              * @return this builder
4489              */
4490             public CatchBuilder catch_(TypeElement exceptionType, Consumer<Block.Builder> c) {
4491                 Body.Builder _catch = Body.Builder.of(ancestorBody,
4492                         CoreType.functionType(VOID, exceptionType));
4493                 c.accept(_catch.entryBlock());
4494                 catchers.add(_catch);
4495 
4496                 return this;
4497             }
4498 
4499             /**
4500              * Adds a finalizer clause to the try operation.
4501              *
4502              * @param c a consumer to populate the finalizer body
4503              * @return the complete try operation with finalizer
4504              */
4505             public TryOp finally_(Consumer<Block.Builder> c) {
4506                 Body.Builder _finally = Body.Builder.of(ancestorBody, CoreType.FUNCTION_TYPE_VOID);
4507                 c.accept(_finally.entryBlock());
4508 
4509                 return new TryOp(resources, body, catchers, _finally);
4510             }
4511 
4512             /**
4513              * Completes the try operation without a finalizer clause.
4514              *
4515              * @return the complete try operation without finalizer
4516              */
4517             public TryOp noFinalizer() {
4518                 return new TryOp(resources, body, catchers, null);
4519             }
4520         }
4521 
4522         static final String NAME = "java.try";
4523 
4524         final Body resources;
4525         final Body body;
4526         final List<Body> catchers;
4527         final Body finalizer;
4528 
4529         TryOp(ExternalizedOp def) {
4530             List<Body.Builder> bodies = def.bodyDefinitions();
4531             Body.Builder first = bodies.getFirst();
4532             Body.Builder resources;
4533             Body.Builder body;
4534             if (first.bodyType().returnType().equals(VOID)) {
4535                 resources = null;
4536                 body = first;
4537             } else {
4538                 resources = first;
4539                 body = bodies.get(1);
4540             }
4541 
4542             Body.Builder last = bodies.getLast();
4543             Body.Builder finalizer;
4544             if (last.bodyType().parameterTypes().isEmpty()) {
4545                 finalizer = last;
4546             } else {
4547                 finalizer = null;
4548             }
4549             List<Body.Builder> catchers = bodies.subList(
4550                     resources == null ? 1 : 2,
4551                     bodies.size() - (finalizer == null ? 0 : 1));
4552 
4553             this(resources, body, catchers, finalizer);
4554         }
4555 
4556         TryOp(TryOp that, CodeContext cc, CodeTransformer ot) {
4557             super(that, cc);
4558 
4559             if (that.resources != null) {
4560                 this.resources = that.resources.transform(cc, ot).build(this);
4561             } else {
4562                 this.resources = null;
4563             }
4564             this.body = that.body.transform(cc, ot).build(this);
4565             this.catchers = that.catchers.stream()
4566                     .map(b -> b.transform(cc, ot).build(this))
4567                     .toList();
4568             if (that.finalizer != null) {
4569                 this.finalizer = that.finalizer.transform(cc, ot).build(this);
4570             } else {
4571                 this.finalizer = null;
4572             }
4573         }
4574 
4575         @Override
4576         public TryOp transform(CodeContext cc, CodeTransformer ot) {
4577             return new TryOp(this, cc, ot);
4578         }
4579 
4580         TryOp(Body.Builder resourcesC,
4581               Body.Builder bodyC,
4582               List<Body.Builder> catchersC,
4583               Body.Builder finalizerC) {
4584             super(List.of());
4585 
4586             if (resourcesC != null) {
4587                 this.resources = resourcesC.build(this);
4588                 if (resources.bodyType().returnType().equals(VOID)) {
4589                     throw new IllegalArgumentException("Resources should not return void: " + resources.bodyType());
4590                 }
4591                 if (!resources.bodyType().parameterTypes().isEmpty()) {
4592                     throw new IllegalArgumentException("Resources should have zero parameters: " + resources.bodyType());
4593                 }
4594             } else {
4595                 this.resources = null;
4596             }
4597 
4598             this.body = bodyC.build(this);
4599             if (!body.bodyType().returnType().equals(VOID)) {
4600                 throw new IllegalArgumentException("Try should return void: " + body.bodyType());
4601             }
4602 
4603             this.catchers = catchersC.stream().map(c -> c.build(this)).toList();
4604             for (Body _catch : catchers) {
4605                 if (!_catch.bodyType().returnType().equals(VOID)) {
4606                     throw new IllegalArgumentException("Catch should return void: " + _catch.bodyType());
4607                 }
4608                 if (_catch.bodyType().parameterTypes().size() != 1) {
4609                     throw new IllegalArgumentException("Catch should have zero parameters: " + _catch.bodyType());
4610                 }
4611             }
4612 
4613             if (finalizerC != null) {
4614                 this.finalizer = finalizerC.build(this);
4615                 if (!finalizer.bodyType().returnType().equals(VOID)) {
4616                     throw new IllegalArgumentException("Finally should return void: " + finalizer.bodyType());
4617                 }
4618                 if (!finalizer.bodyType().parameterTypes().isEmpty()) {
4619                     throw new IllegalArgumentException("Finally should have zero parameters: " + finalizer.bodyType());
4620                 }
4621             } else {
4622                 this.finalizer = null;
4623             }
4624         }
4625 
4626         @Override
4627         public List<Body> bodies() {
4628             ArrayList<Body> bodies = new ArrayList<>();
4629             if (resources != null) {
4630                 bodies.add(resources);
4631             }
4632             bodies.add(body);
4633             bodies.addAll(catchers);
4634             if (finalizer != null) {
4635                 bodies.add(finalizer);
4636             }
4637             return bodies;
4638         }
4639 
4640         /**
4641          * {@return the resources body, or {@code null} if this try operation has no resources}
4642          */
4643         public Body resources() {
4644             return resources;
4645         }
4646 
4647         /**
4648          * {@return the body of the try operation}
4649          */
4650         public Body body() {
4651             return body;
4652         }
4653 
4654         /**
4655          * {@return the catch clause bodies}
4656          */
4657         public List<Body> catchers() {
4658             return catchers;
4659         }
4660 
4661         /**
4662          * {@return the finalizer body, or {@code null} if this try operation has no finalizer}
4663          */
4664         public Body finalizer() {
4665             return finalizer;
4666         }
4667 
4668         @Override
4669         public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
4670             if (resources != null) {
4671                 throw new UnsupportedOperationException("Lowering of try-with-resources is unsupported");
4672             }
4673 
4674             Block.Builder exit = b.block();
4675             BranchTarget.setBranchTarget(b.context(), this, exit, null);
4676 
4677             // Simple case with no catch and finally bodies
4678             if (catchers.isEmpty() && finalizer == null) {
4679                 b.body(body, List.of(), andThenLowering(opT, (block, op) -> {
4680                     if (op instanceof CoreOp.YieldOp) {
4681                         block.op(branch(exit.successor()));
4682                         return block;
4683                     } else {
4684                         return null;
4685                     }
4686                 }));
4687                 return exit;
4688             }
4689 
4690             Block.Builder tryRegionEnter = b.block();
4691             Block.Builder tryRegionExit = b.block();
4692 
4693             // Construct the catcher block builders
4694             List<Block.Builder> catchers = catchers().stream()
4695                     .map(catcher -> b.block())
4696                     .toList();
4697             Block.Builder catcherFinally;
4698             if (finalizer == null) {
4699                 catcherFinally = null;
4700             } else {
4701                 catcherFinally = b.block();
4702                 catchers = new ArrayList<>(catchers);
4703                 catchers.add(catcherFinally);
4704             }
4705 
4706             // Enter the try exception region
4707             List<Block.Reference> exitHandlers = catchers.stream()
4708                     .map(Block.Builder::successor)
4709                     .toList();
4710             b.op(exceptionRegionEnter(tryRegionEnter.successor(), exitHandlers.reversed()));
4711 
4712             CodeTransformer tryExitTransformer;
4713             if (finalizer != null) {
4714                 tryExitTransformer = compose(opT, (block, op) -> {
4715                     if (op instanceof CoreOp.ReturnOp ||
4716                             (op instanceof JavaOp.JavaLabelOp lop && ifExitFromTry(lop))) {
4717                         return inlineFinalizer(block, exitHandlers, opT);
4718                     } else {
4719                         return block;
4720                     }
4721                 });
4722             } else {
4723                 tryExitTransformer = compose(opT, (block, op) -> {
4724                     if (op instanceof CoreOp.ReturnOp ||
4725                             (op instanceof JavaOp.JavaLabelOp lop && ifExitFromTry(lop))) {
4726                         Block.Builder tryRegionReturnExit = block.block();
4727                         block.op(exceptionRegionExit(tryRegionReturnExit.successor(), exitHandlers));
4728                         return tryRegionReturnExit;
4729                     } else {
4730                         return block;
4731                     }
4732                 });
4733             }
4734             // Inline the try body
4735             AtomicBoolean hasTryRegionExit = new AtomicBoolean();
4736             tryRegionEnter.body(body, List.of(), andThenLowering(tryExitTransformer, (block, op) -> {
4737                 if (op instanceof CoreOp.YieldOp) {
4738                     hasTryRegionExit.set(true);
4739                     block.op(branch(tryRegionExit.successor()));
4740                     return block;
4741                 } else {
4742                     return null;
4743                 }
4744             }));
4745 
4746             Block.Builder finallyEnter = null;
4747             if (finalizer != null) {
4748                 finallyEnter = b.block();
4749                 if (hasTryRegionExit.get()) {
4750                     // Exit the try exception region
4751                     tryRegionExit.op(exceptionRegionExit(finallyEnter.successor(), exitHandlers));
4752                 }
4753             } else if (hasTryRegionExit.get()) {
4754                 // Exit the try exception region
4755                 tryRegionExit.op(exceptionRegionExit(exit.successor(), exitHandlers));
4756             }
4757 
4758             // Inline the catch bodies
4759             for (int i = 0; i < this.catchers.size(); i++) {
4760                 Block.Builder catcher = catchers.get(i);
4761                 Body catcherBody = this.catchers.get(i);
4762                 // Create the throwable argument
4763                 Block.Parameter t = catcher.parameter(catcherBody.bodyType().parameterTypes().get(0));
4764 
4765                 if (finalizer != null) {
4766                     Block.Builder catchRegionEnter = b.block();
4767                     Block.Builder catchRegionExit = b.block();
4768 
4769                     // Enter the catch exception region
4770                     Result catchExceptionRegion = catcher.op(
4771                             exceptionRegionEnter(catchRegionEnter.successor(), catcherFinally.successor()));
4772 
4773                     CodeTransformer catchExitTransformer = compose(opT, (block, op) -> {
4774                         if (op instanceof CoreOp.ReturnOp) {
4775                             return inlineFinalizer(block, List.of(catcherFinally.successor()), opT);
4776                         } else if (op instanceof JavaOp.JavaLabelOp lop && ifExitFromTry(lop)) {
4777                             return inlineFinalizer(block, List.of(catcherFinally.successor()), opT);
4778                         } else {
4779                             return block;
4780                         }
4781                     });
4782                     // Inline the catch body
4783                     AtomicBoolean hasCatchRegionExit = new AtomicBoolean();
4784                     catchRegionEnter.body(catcherBody, List.of(t), andThenLowering(catchExitTransformer, (block, op) -> {
4785                         if (op instanceof CoreOp.YieldOp) {
4786                             hasCatchRegionExit.set(true);
4787                             block.op(branch(catchRegionExit.successor()));
4788                             return block;
4789                         } else {
4790                             return null;
4791                         }
4792                     }));
4793 
4794                     // Exit the catch exception region
4795                     if (hasCatchRegionExit.get()) {
4796                         hasTryRegionExit.set(true);
4797                         catchRegionExit.op(exceptionRegionExit(finallyEnter.successor(), catcherFinally.successor()));
4798                     }
4799                 } else {
4800                     // Inline the catch body
4801                     catcher.body(catcherBody, List.of(t), andThenLowering(opT, (block, op) -> {
4802                         if (op instanceof CoreOp.YieldOp) {
4803                             block.op(branch(exit.successor()));
4804                             return block;
4805                         } else {
4806                             return null;
4807                         }
4808                     }));
4809                 }
4810             }
4811 
4812             if (finalizer != null && hasTryRegionExit.get()) {
4813                 // Inline the finally body
4814                 finallyEnter.body(finalizer, List.of(), andThenLowering(opT, (block, op) -> {
4815                     if (op instanceof CoreOp.YieldOp) {
4816                         block.op(branch(exit.successor()));
4817                         return block;
4818                     } else {
4819                         return null;
4820                     }
4821                 }));
4822             }
4823 
4824             // Inline the finally body as a catcher of Throwable and adjusting to throw
4825             if (finalizer != null) {
4826                 // Create the throwable argument
4827                 Block.Parameter t = catcherFinally.parameter(type(Throwable.class));
4828 
4829                 catcherFinally.body(finalizer, List.of(), andThenLowering(opT, (block, op) -> {
4830                     if (op instanceof CoreOp.YieldOp) {
4831                         block.op(throw_(t));
4832                         return block;
4833                     } else {
4834                         return null;
4835                     }
4836                 }));
4837             }
4838             return exit;
4839         }
4840 
4841         boolean ifExitFromTry(JavaLabelOp lop) {
4842             Op target = lop.target();
4843             return target == this || target.isAncestorOf(this);
4844         }
4845 
4846         Block.Builder inlineFinalizer(Block.Builder block1, List<Block.Reference> tryHandlers, CodeTransformer opT) {
4847             Block.Builder finallyEnter = block1.block();
4848             Block.Builder finallyExit = block1.block();
4849 
4850             block1.op(exceptionRegionExit(finallyEnter.successor(), tryHandlers));
4851 
4852             // Inline the finally body
4853             finallyEnter.body(finalizer, List.of(), andThenLowering(opT, (block2, op2) -> {
4854                 if (op2 instanceof CoreOp.YieldOp) {
4855                     block2.op(branch(finallyExit.successor()));
4856                     return block2;
4857                 } else {
4858                     return null;
4859                 }
4860             }));
4861 
4862             return finallyExit;
4863         }
4864 
4865         @Override
4866         public TypeElement resultType() {
4867             return VOID;
4868         }
4869     }
4870 
4871     //
4872     // Patterns
4873 
4874     // Reified pattern nodes
4875 
4876     /**
4877      * Synthetic pattern types
4878      * // @@@ Replace with types extending from TypeElement
4879      */
4880     public sealed interface Pattern {
4881 
4882         /**
4883          * Synthetic type pattern type.
4884          *
4885          * @param <T> the type of values that are bound
4886          */
4887         final class Type<T> implements Pattern {
4888             Type() {
4889             }
4890         }
4891 
4892         /**
4893          * Synthetic record pattern type.
4894          *
4895          * @param <T> the type of records that are bound
4896          */
4897         final class Record<T> implements Pattern {
4898             Record() {
4899             }
4900         }
4901 
4902         /**
4903          * A synthetic match-all pattern type representing an unconditional pattern.
4904          */
4905         final class MatchAll implements Pattern {
4906             MatchAll() {
4907             }
4908         }
4909 
4910         // @@@ Pattern types
4911 
4912         /** The synthetic type of a type test pattern. */
4913         JavaType PATTERN_BINDING_TYPE = JavaType.type(Type.class);
4914 
4915         /** The synthetic type of a record pattern. */
4916         JavaType PATTERN_RECORD_TYPE = JavaType.type(Record.class);
4917 
4918         /** The synthetic type of an unconditional pattern. */
4919         JavaType PATTERN_MATCH_ALL_TYPE = JavaType.type(MatchAll.class);
4920 
4921         /**
4922          * {@return a synthetic type for a type test pattern with the provided type}
4923          * @param t the type of the type test pattern
4924          */
4925         static JavaType bindingType(TypeElement t) {
4926             return parameterized(PATTERN_BINDING_TYPE, (JavaType) t);
4927         }
4928 
4929         /**
4930          * {@return a synthetic type for a record pattern with the provided record type}
4931          * @param t the record type
4932          */
4933         static JavaType recordType(TypeElement t) {
4934             return parameterized(PATTERN_RECORD_TYPE, (JavaType) t);
4935         }
4936 
4937         /**
4938          * {@return a synthetic type for an unconditional pattern}
4939          */
4940         static JavaType matchAllType() {
4941             return PATTERN_MATCH_ALL_TYPE;
4942         }
4943 
4944         /**
4945          * {@return the type bound by a synthetic type test/record pattern}
4946          * @param t the synthetic pattern type
4947          */
4948         static TypeElement targetType(TypeElement t) {
4949             return ((ClassType) t).typeArguments().get(0);
4950         }
4951     }
4952 
4953     /**
4954      * Pattern operations.
4955      */
4956     public static final class PatternOps {
4957         PatternOps() {
4958         }
4959 
4960         /**
4961          * The pattern operation.
4962          */
4963         public sealed static abstract class PatternOp extends JavaOp implements Op.Pure {
4964             PatternOp(PatternOp that, CodeContext cc) {
4965                 super(that, cc);
4966             }
4967 
4968             PatternOp(List<Value> operands) {
4969                 super(operands);
4970             }
4971         }
4972 
4973         /**
4974          * The binding pattern operation, that can model Java language type test patterns.
4975          */
4976         @OpDeclaration(TypePatternOp.NAME)
4977         public static final class TypePatternOp extends PatternOp {
4978             static final String NAME = "pattern.type";
4979 
4980             /**
4981              * The externalized attribute key for a pattern binding name in a type pattern operation.
4982              */
4983             public static final String ATTRIBUTE_BINDING_NAME = NAME + ".binding.name";
4984 
4985             final TypeElement resultType;
4986             final String bindingName;
4987 
4988             TypePatternOp(ExternalizedOp def) {
4989                 super(List.of());
4990 
4991                 this.bindingName = def.extractAttributeValue(ATTRIBUTE_BINDING_NAME, true,
4992                         v -> switch (v) {
4993                             case String s -> s;
4994                             case null -> null;
4995                             default -> throw new UnsupportedOperationException("Unsupported pattern binding name value:" + v);
4996                         });
4997                 // @@@ Cannot use canonical constructor because it wraps the given type
4998                 this.resultType = def.resultType();
4999             }
5000 
5001             TypePatternOp(TypePatternOp that, CodeContext cc) {
5002                 super(that, cc);
5003 
5004                 this.bindingName = that.bindingName;
5005                 this.resultType = that.resultType;
5006             }
5007 
5008             @Override
5009             public TypePatternOp transform(CodeContext cc, CodeTransformer ot) {
5010                 return new TypePatternOp(this, cc);
5011             }
5012 
5013             TypePatternOp(TypeElement targetType, String bindingName) {
5014                 super(List.of());
5015 
5016                 this.bindingName = bindingName;
5017                 this.resultType = Pattern.bindingType(targetType);
5018             }
5019 
5020             @Override
5021             public Map<String, Object> externalize() {
5022                 return bindingName == null ? Map.of() : Map.of("", bindingName);
5023             }
5024 
5025             /**
5026              * {@return the variable name bound by this type test pattern, or {@code null} if none}
5027              */
5028             public String bindingName() {
5029                 return bindingName;
5030             }
5031 
5032             /**
5033              * {@return the type matched by this type test pattern}
5034              */
5035             public TypeElement targetType() {
5036                 return Pattern.targetType(resultType());
5037             }
5038 
5039             @Override
5040             public TypeElement resultType() {
5041                 return resultType;
5042             }
5043         }
5044 
5045         /**
5046          * The record pattern operation, that can model Java language record patterns.
5047          */
5048         @OpDeclaration(RecordPatternOp.NAME)
5049         public static final class RecordPatternOp extends PatternOp {
5050             static final String NAME = "pattern.record";
5051 
5052             /**
5053              * The externalized attribute key for a record descriptor in a record pattern operation.
5054              */
5055             public static final String ATTRIBUTE_RECORD_DESCRIPTOR = NAME + ".descriptor";
5056 
5057             final RecordTypeRef recordDescriptor;
5058 
5059             RecordPatternOp(ExternalizedOp def) {
5060                 RecordTypeRef recordDescriptor = def.extractAttributeValue(ATTRIBUTE_RECORD_DESCRIPTOR, true,
5061                         v -> switch (v) {
5062                             case RecordTypeRef rtd -> rtd;
5063                             case null, default ->
5064                                     throw new UnsupportedOperationException("Unsupported record type descriptor value:" + v);
5065                         });
5066 
5067                 this(recordDescriptor, def.operands());
5068             }
5069 
5070             RecordPatternOp(RecordPatternOp that, CodeContext cc) {
5071                 super(that, cc);
5072 
5073                 this.recordDescriptor = that.recordDescriptor;
5074             }
5075 
5076             @Override
5077             public RecordPatternOp transform(CodeContext cc, CodeTransformer ot) {
5078                 return new RecordPatternOp(this, cc);
5079             }
5080 
5081             RecordPatternOp(RecordTypeRef recordDescriptor, List<Value> nestedPatterns) {
5082                 // The type of each value is a subtype of Pattern
5083                 // The number of values corresponds to the number of components of the record
5084                 super(List.copyOf(nestedPatterns));
5085 
5086                 this.recordDescriptor = recordDescriptor;
5087             }
5088 
5089             @Override
5090             public Map<String, Object> externalize() {
5091                 return Map.of("", recordDescriptor());
5092             }
5093 
5094             /**
5095              * {@return the record descriptor associated with this record pattern}
5096              */
5097             public RecordTypeRef recordDescriptor() {
5098                 return recordDescriptor;
5099             }
5100 
5101             /**
5102              * {@return the type matched by this record pattern}
5103              */
5104             public TypeElement targetType() {
5105                 return Pattern.targetType(resultType());
5106             }
5107 
5108             @Override
5109             public TypeElement resultType() {
5110                 return Pattern.recordType(recordDescriptor.recordType());
5111             }
5112         }
5113 
5114         /**
5115          * A pattern operation representing a match-all (unconditional) pattern.
5116          */
5117         @OpDeclaration(MatchAllPatternOp.NAME)
5118         public static final class MatchAllPatternOp extends PatternOp {
5119 
5120             // @@@ we may need to add info about the type of the record component
5121             // this info can be used when lowering
5122 
5123             static final String NAME = "pattern.match.all";
5124 
5125             MatchAllPatternOp(ExternalizedOp def) {
5126                 this();
5127             }
5128 
5129             MatchAllPatternOp(MatchAllPatternOp that, CodeContext cc) {
5130                 super(that, cc);
5131             }
5132 
5133             MatchAllPatternOp() {
5134                 super(List.of());
5135             }
5136 
5137             @Override
5138             public Op transform(CodeContext cc, CodeTransformer ot) {
5139                 return new MatchAllPatternOp(this, cc);
5140             }
5141 
5142             @Override
5143             public TypeElement resultType() {
5144                 return Pattern.matchAllType();
5145             }
5146         }
5147 
5148         /**
5149          * The match operation, that can model Java language pattern matching.
5150          */
5151         @OpDeclaration(MatchOp.NAME)
5152         public static final class MatchOp extends JavaOp implements Op.Isolated, Op.Lowerable {
5153             static final String NAME = "pattern.match";
5154 
5155             final Body pattern;
5156             final Body match;
5157 
5158             MatchOp(ExternalizedOp def) {
5159                 this(def.operands().get(0),
5160                         def.bodyDefinitions().get(0), def.bodyDefinitions().get(1));
5161             }
5162 
5163             MatchOp(MatchOp that, CodeContext cc, CodeTransformer ot) {
5164                 super(that, cc);
5165 
5166                 this.pattern = that.pattern.transform(cc, ot).build(this);
5167                 this.match = that.match.transform(cc, ot).build(this);
5168             }
5169 
5170             @Override
5171             public MatchOp transform(CodeContext cc, CodeTransformer ot) {
5172                 return new MatchOp(this, cc, ot);
5173             }
5174 
5175             MatchOp(Value target, Body.Builder patternC, Body.Builder matchC) {
5176                 super(List.of(target));
5177 
5178                 this.pattern = patternC.build(this);
5179                 this.match = matchC.build(this);
5180             }
5181 
5182             @Override
5183             public List<Body> bodies() {
5184                 return List.of(pattern, match);
5185             }
5186 
5187             /**
5188              * Returns the pattern body for this match operation.
5189              * @return the pattern body
5190              */
5191             public Body pattern() {
5192                 return pattern;
5193             }
5194 
5195             /**
5196              * Returns the match body for this match operation.
5197              * @return the match body
5198              */
5199             public Body match() {
5200                 return match;
5201             }
5202 
5203             /**
5204              * Returns the target value being matched in this match operation.
5205              * @return the match target value
5206              */
5207             public Value target() {
5208                 return operands().get(0);
5209             }
5210 
5211             @Override
5212             public Block.Builder lower(Block.Builder b, CodeTransformer opT) {
5213                 // No match block
5214                 Block.Builder endNoMatchBlock = b.block();
5215                 // Match block
5216                 Block.Builder endMatchBlock = b.block();
5217                 // End block
5218                 Block.Builder endBlock = b.block();
5219                 Block.Parameter matchResult = endBlock.parameter(resultType());
5220                 // Map match operation result
5221                 b.context().mapValue(result(), matchResult);
5222 
5223                 List<Value> patternValues = new ArrayList<>();
5224                 Op patternYieldOp = pattern.entryBlock().terminatingOp();
5225                 Op.Result rootPatternValue = (Op.Result) patternYieldOp.operands().get(0);
5226                 Block.Builder currentBlock = lower(endNoMatchBlock, b,
5227                         patternValues,
5228                         rootPatternValue.op(),
5229                         b.context().getValue(target()));
5230                 currentBlock.op(branch(endMatchBlock.successor()));
5231 
5232                 // No match block
5233                 // Pass false
5234                 endNoMatchBlock.op(branch(endBlock.successor(
5235                         endNoMatchBlock.op(constant(BOOLEAN, false)))));
5236 
5237                 // Match block
5238                 // Lower match body and pass true
5239                 endMatchBlock.body(match, patternValues, andThenLowering(opT, (block, op) -> {
5240                     if (op instanceof CoreOp.YieldOp) {
5241                         block.op(branch(endBlock.successor(
5242                                 block.op(constant(BOOLEAN, true)))));
5243                         return block;
5244                     } else {
5245                         return null;
5246                     }
5247                 }));
5248 
5249                 return endBlock;
5250             }
5251 
5252             static Block.Builder lower(Block.Builder endNoMatchBlock, Block.Builder currentBlock,
5253                                        List<Value> bindings,
5254                                        Op pattern, Value target) {
5255                 return switch (pattern) {
5256                     case RecordPatternOp rp -> lowerRecordPattern(endNoMatchBlock, currentBlock, bindings, rp, target);
5257                     case TypePatternOp tp -> lowerTypePattern(endNoMatchBlock, currentBlock, bindings, tp, target);
5258                     case MatchAllPatternOp map -> lowerMatchAllPattern(currentBlock);
5259                     case null, default -> throw new UnsupportedOperationException("Unknown pattern op: " + pattern);
5260                 };
5261             }
5262 
5263             static Block.Builder lowerRecordPattern(Block.Builder endNoMatchBlock, Block.Builder currentBlock,
5264                                                     List<Value> bindings,
5265                                                     JavaOp.PatternOps.RecordPatternOp rpOp, Value target) {
5266                 TypeElement targetType = rpOp.targetType();
5267 
5268                 Block.Builder nextBlock = currentBlock.block();
5269 
5270                 // Check if instance of target type
5271                 Op.Result isInstance = currentBlock.op(instanceOf(targetType, target));
5272                 currentBlock.op(conditionalBranch(isInstance, nextBlock.successor(), endNoMatchBlock.successor()));
5273 
5274                 currentBlock = nextBlock;
5275 
5276                 target = currentBlock.op(cast(targetType, target));
5277 
5278                 // Access component values of record and match on each as nested target
5279                 List<Value> dArgs = rpOp.operands();
5280                 for (int i = 0; i < dArgs.size(); i++) {
5281                     Op.Result nestedPattern = (Op.Result) dArgs.get(i);
5282                     // @@@ Handle exceptions?
5283                     Value nestedTarget = currentBlock.op(invoke(rpOp.recordDescriptor().methodForComponent(i), target));
5284 
5285                     currentBlock = lower(endNoMatchBlock, currentBlock, bindings, nestedPattern.op(), nestedTarget);
5286                 }
5287 
5288                 return currentBlock;
5289             }
5290 
5291             static Block.Builder lowerTypePattern(Block.Builder endNoMatchBlock, Block.Builder currentBlock,
5292                                                   List<Value> bindings,
5293                                                   TypePatternOp tpOp, Value target) {
5294                 TypeElement targetType = tpOp.targetType();
5295 
5296                 // Check if instance of target type
5297                 Op p; // op that perform type check
5298                 Op c; // op that perform conversion
5299                 TypeElement s = target.type();
5300                 TypeElement t = targetType;
5301                 if (t instanceof PrimitiveType pt) {
5302                     if (s instanceof ClassType cs) {
5303                         // unboxing conversions
5304                         ClassType box;
5305                         if (cs.unbox().isEmpty()) { // s not a boxed type
5306                             // e.g. Number -> int, narrowing + unboxing
5307                             box = pt.box().orElseThrow();
5308                             p = instanceOf(box, target);
5309                         } else {
5310                             // e.g. Float -> float, unboxing
5311                             // e.g. Integer -> long, unboxing + widening
5312                             box = cs;
5313                             p = null;
5314                         }
5315                         c = invoke(MethodRef.method(box, t + "Value", t), target);
5316                     } else {
5317                         // primitive to primitive conversion
5318                         PrimitiveType ps = ((PrimitiveType) s);
5319                         if (isNarrowingPrimitiveConv(ps, pt) || isWideningPrimitiveConvWithCheck(ps, pt)
5320                                 || isWideningAndNarrowingPrimitiveConv(ps, pt)) {
5321                             // e.g. int -> byte, narrowing
5322                             // e,g. int -> float, widening with check
5323                             // e.g. byte -> char, widening and narrowing
5324                             MethodRef mref = convMethodRef(s, t);
5325                             p = invoke(mref, target);
5326                         } else {
5327                             p = null;
5328                         }
5329                         c = conv(targetType, target);
5330                     }
5331                 } else if (s instanceof PrimitiveType ps) {
5332                     // boxing conversions
5333                     // e.g. int -> Number, boxing + widening
5334                     // e.g. byte -> Byte, boxing
5335                     p = null;
5336                     ClassType box = ps.box().orElseThrow();
5337                     c = invoke(MethodRef.method(box, "valueOf", box, ps), target);
5338                 } else if (!s.equals(t)) {
5339                     // reference to reference, but not identity
5340                     // e.g. Number -> Double, narrowing
5341                     // e.g. Short -> Object, widening
5342                     p = instanceOf(targetType, target);
5343                     c = cast(targetType, target);
5344                 } else {
5345                     // identity reference
5346                     // e.g. Character -> Character
5347                     p = null;
5348                     c = null;
5349                 }
5350 
5351                 if (c != null) {
5352                     if (p != null) {
5353                         // p != null, we need to perform type check at runtime
5354                         Block.Builder nextBlock = currentBlock.block();
5355                         currentBlock.op(conditionalBranch(currentBlock.op(p), nextBlock.successor(), endNoMatchBlock.successor()));
5356                         currentBlock = nextBlock;
5357                     }
5358                     target = currentBlock.op(c);
5359                 }
5360 
5361                 bindings.add(target);
5362 
5363                 return currentBlock;
5364             }
5365 
5366             private static boolean isWideningAndNarrowingPrimitiveConv(PrimitiveType s, PrimitiveType t) {
5367                 return BYTE.equals(s) && CHAR.equals(t);
5368             }
5369 
5370             private static boolean isWideningPrimitiveConvWithCheck(PrimitiveType s, PrimitiveType t) {
5371                 return (INT.equals(s) && FLOAT.equals(t))
5372                         || (LONG.equals(s) && FLOAT.equals(t))
5373                         || (LONG.equals(s) && DOUBLE.equals(t));
5374             }
5375 
5376             // s -> t is narrowing if order(t) <= order(s)
5377             private final static Map<PrimitiveType, Integer> narrowingOrder = Map.of(
5378                     BYTE, 1,
5379                     SHORT, 2,
5380                     CHAR, 2,
5381                     INT, 3,
5382                     LONG, 4,
5383                     FLOAT, 5,
5384                     DOUBLE, 6
5385             );
5386             private static boolean isNarrowingPrimitiveConv(PrimitiveType s, PrimitiveType t) {
5387                 return narrowingOrder.get(t) <= narrowingOrder.get(s) && !s.equals(t); // need to be strict, to not consider int -> int as narrowing
5388             }
5389 
5390             private static MethodRef convMethodRef(TypeElement s, TypeElement t) {
5391                 if (BYTE.equals(s) || SHORT.equals(s) || CHAR.equals(s)) {
5392                     s = INT;
5393                 }
5394                 String sn = capitalize(s.toString());
5395                 String tn = capitalize(t.toString());
5396                 String mn = "is%sTo%sExact".formatted(sn, tn);
5397                 JavaType exactConversionSupport = JavaType.type(ClassDesc.of("java.lang.runtime.ExactConversionsSupport"));
5398                 return MethodRef.method(exactConversionSupport, mn, BOOLEAN, s);
5399             }
5400 
5401             private static String capitalize(String s) {
5402                 return s.substring(0, 1).toUpperCase() + s.substring(1);
5403             }
5404 
5405             static Block.Builder lowerMatchAllPattern(Block.Builder currentBlock) {
5406                 return currentBlock;
5407             }
5408 
5409             @Override
5410             public TypeElement resultType() {
5411                 return BOOLEAN;
5412             }
5413         }
5414     }
5415 
5416     static Op createOp(ExternalizedOp def) {
5417         Op op = switch (def.name()) {
5418             case "add" -> new AddOp(def);
5419             case "and" -> new AndOp(def);
5420             case "array.length" -> new ArrayLengthOp(def);
5421             case "array.load" -> new ArrayAccessOp.ArrayLoadOp(def);
5422             case "array.store" -> new ArrayAccessOp.ArrayStoreOp(def);
5423             case "ashr" -> new AshrOp(def);
5424             case "assert" -> new AssertOp(def);
5425             case "cast" -> new CastOp(def);
5426             case "compl" -> new ComplOp(def);
5427             case "concat" -> new ConcatOp(def);
5428             case "conv" -> new ConvOp(def);
5429             case "div" -> new DivOp(def);
5430             case "eq" -> new EqOp(def);
5431             case "exception.region.enter" -> new ExceptionRegionEnter(def);
5432             case "exception.region.exit" -> new ExceptionRegionExit(def);
5433             case "field.load" -> new FieldAccessOp.FieldLoadOp(def);
5434             case "field.store" -> new FieldAccessOp.FieldStoreOp(def);
5435             case "ge" -> new GeOp(def);
5436             case "gt" -> new GtOp(def);
5437             case "instanceof" -> new InstanceOfOp(def);
5438             case "invoke" -> new InvokeOp(def);
5439             case "java.block" -> new BlockOp(def);
5440             case "java.break" -> new BreakOp(def);
5441             case "java.cand" -> new ConditionalAndOp(def);
5442             case "java.cexpression" -> new ConditionalExpressionOp(def);
5443             case "java.continue" -> new ContinueOp(def);
5444             case "java.cor" -> new ConditionalOrOp(def);
5445             case "java.do.while" -> new DoWhileOp(def);
5446             case "java.enhancedFor" -> new EnhancedForOp(def);
5447             case "java.for" -> new ForOp(def);
5448             case "java.if" -> new IfOp(def);
5449             case "java.labeled" -> new LabeledOp(def);
5450             case "java.switch.expression" -> new SwitchExpressionOp(def);
5451             case "java.switch.fallthrough" -> new SwitchFallthroughOp(def);
5452             case "java.switch.statement" -> new SwitchStatementOp(def);
5453             case "java.synchronized" -> new SynchronizedOp(def);
5454             case "java.try" -> new TryOp(def);
5455             case "java.while" -> new WhileOp(def);
5456             case "java.yield" -> new YieldOp(def);
5457             case "lambda" -> new LambdaOp(def);
5458             case "le" -> new LeOp(def);
5459             case "lshl" -> new LshlOp(def);
5460             case "lshr" -> new LshrOp(def);
5461             case "lt" -> new LtOp(def);
5462             case "mod" -> new ModOp(def);
5463             case "monitor.enter" -> new MonitorOp.MonitorEnterOp(def);
5464             case "monitor.exit" -> new MonitorOp.MonitorExitOp(def);
5465             case "mul" -> new MulOp(def);
5466             case "neg" -> new NegOp(def);
5467             case "neq" -> new NeqOp(def);
5468             case "new" -> new NewOp(def);
5469             case "not" -> new NotOp(def);
5470             case "or" -> new OrOp(def);
5471             case "pattern.match" -> new PatternOps.MatchOp(def);
5472             case "pattern.match.all" -> new PatternOps.MatchAllPatternOp(def);
5473             case "pattern.record" -> new PatternOps.RecordPatternOp(def);
5474             case "pattern.type" -> new PatternOps.TypePatternOp(def);
5475             case "sub" -> new SubOp(def);
5476             case "throw" -> new ThrowOp(def);
5477             case "xor" -> new XorOp(def);
5478             default -> null;
5479         };
5480         if (op != null) {
5481             op.setLocation(def.location());
5482         }
5483         return op;
5484     }
5485 
5486     /**
5487      * An operation factory for core operations composed with Java operations.
5488      */
5489     public static final OpFactory JAVA_OP_FACTORY = CoreOp.CORE_OP_FACTORY.andThen(JavaOp::createOp);
5490 
5491     /**
5492      * A Java dialect factory, for constructing core and Java operations and constructing
5493      * core type and Java type elements, where the core type elements can refer to Java
5494      * type elements.
5495      */
5496     public static final DialectFactory JAVA_DIALECT_FACTORY = new DialectFactory(
5497             JAVA_OP_FACTORY,
5498             JAVA_TYPE_FACTORY);
5499 
5500     /**
5501      * Creates a lambda operation.
5502      *
5503      * @param ancestorBody        the ancestor of the body of the lambda operation
5504      * @param funcType            the lambda operation's function type
5505      * @param functionalInterface the lambda operation's functional interface type
5506      * @return the lambda operation
5507      */
5508     public static LambdaOp.Builder lambda(Body.Builder ancestorBody,
5509                                           FunctionType funcType, TypeElement functionalInterface) {
5510         return new LambdaOp.Builder(ancestorBody, funcType, functionalInterface);
5511     }
5512 
5513     /**
5514      * Creates a lambda operation.
5515      *
5516      * @param functionalInterface the lambda operation's functional interface type
5517      * @param body                the body of the lambda operation
5518      * @return the lambda operation
5519      */
5520     public static LambdaOp lambda(TypeElement functionalInterface, Body.Builder body) {
5521         return new LambdaOp(functionalInterface, body, false);
5522     }
5523 
5524     /**
5525      * Creates a lambda operation.
5526      *
5527      * @param functionalInterface the lambda operation's functional interface type
5528      * @param body                the body of the lambda operation
5529      * @param isReflectable       true if the lambda is reflectable
5530      * @return the lambda operation
5531      */
5532     public static LambdaOp lambda(TypeElement functionalInterface, Body.Builder body, boolean isReflectable) {
5533         return new LambdaOp(functionalInterface, body, isReflectable);
5534     }
5535 
5536     /**
5537      * Creates an exception region enter operation
5538      *
5539      * @param start    the exception region block
5540      * @param catchers the blocks handling exceptions thrown by the region block
5541      * @return the exception region enter operation
5542      */
5543     public static ExceptionRegionEnter exceptionRegionEnter(Block.Reference start, Block.Reference... catchers) {
5544         return exceptionRegionEnter(start, List.of(catchers));
5545     }
5546 
5547     /**
5548      * Creates an exception region enter operation
5549      *
5550      * @param start    the exception region block
5551      * @param catchers the blocks handling exceptions thrown by the region block
5552      * @return the exception region enter operation
5553      */
5554     public static ExceptionRegionEnter exceptionRegionEnter(Block.Reference start, List<Block.Reference> catchers) {
5555         List<Block.Reference> s = new ArrayList<>();
5556         s.add(start);
5557         s.addAll(catchers);
5558         return new ExceptionRegionEnter(s);
5559     }
5560 
5561     /**
5562      * Creates an exception region exit operation
5563      *
5564      * @param end             the block to which control is transferred after the exception region is exited
5565      * @param catchers the blocks handling exceptions thrown by the region block
5566      * @return the exception region exit operation
5567      */
5568     public static ExceptionRegionExit exceptionRegionExit(Block.Reference end, Block.Reference... catchers) {
5569         return exceptionRegionExit(end, List.of(catchers));
5570     }
5571 
5572     /**
5573      * Creates an exception region exit operation
5574      *
5575      * @param end             the block to which control is transferred after the exception region is exited
5576      * @param catchers the blocks handling exceptions thrown by the region block
5577      * @return the exception region exit operation
5578      */
5579     public static ExceptionRegionExit exceptionRegionExit(Block.Reference end, List<Block.Reference> catchers) {
5580         List<Block.Reference> s = new ArrayList<>();
5581         s.add(end);
5582         s.addAll(catchers);
5583         return new ExceptionRegionExit(s);
5584     }
5585 
5586     /**
5587      * Creates a throw operation.
5588      *
5589      * @param exceptionValue the thrown value
5590      * @return the throw operation
5591      */
5592     public static ThrowOp throw_(Value exceptionValue) {
5593         return new ThrowOp(exceptionValue);
5594     }
5595 
5596     /**
5597      * Creates an assert operation.
5598      *
5599      * @param bodies the nested bodies
5600      * @return the assert operation
5601      */
5602     public static AssertOp assert_(List<Body.Builder> bodies) {
5603         return new AssertOp(bodies);
5604     }
5605 
5606     /**
5607      * Creates a monitor enter operation.
5608      * @param monitor the monitor value
5609      * @return the monitor enter operation
5610      */
5611     public static MonitorOp.MonitorEnterOp monitorEnter(Value monitor) {
5612         return new MonitorOp.MonitorEnterOp(monitor);
5613     }
5614 
5615     /**
5616      * Creates a monitor exit operation.
5617      * @param monitor the monitor value
5618      * @return the monitor exit operation
5619      */
5620     public static MonitorOp.MonitorExitOp monitorExit(Value monitor) {
5621         return new MonitorOp.MonitorExitOp(monitor);
5622     }
5623 
5624     /**
5625      * Creates an invoke operation modeling an invocation to an
5626      * instance or static (class) method with no variable arguments.
5627      * <p>
5628      * The invoke kind of the invoke operation is determined by
5629      * comparing the argument count with the invoke descriptor's
5630      * parameter count. If they are equal then the invoke kind is
5631      * {@link InvokeOp.InvokeKind#STATIC static}. If the parameter count
5632      * plus one is equal to the argument count then the invoke kind
5633      * is {@link InvokeOp.InvokeKind#STATIC instance}.
5634      * <p>
5635      * The invoke return type is the invoke descriptors return type.
5636      *
5637      * @param invokeDescriptor the invoke descriptor
5638      * @param args             the invoke parameters
5639      * @return the invoke operation
5640      */
5641     public static InvokeOp invoke(MethodRef invokeDescriptor, Value... args) {
5642         return invoke(invokeDescriptor, List.of(args));
5643     }
5644 
5645     /**
5646      * Creates an invoke operation modeling an invocation to an
5647      * instance or static (class) method with no variable arguments.
5648      * <p>
5649      * The invoke kind of the invoke operation is determined by
5650      * comparing the argument count with the invoke descriptor's
5651      * parameter count. If they are equal then the invoke kind is
5652      * {@link InvokeOp.InvokeKind#STATIC static}. If the parameter count
5653      * plus one is equal to the argument count then the invoke kind
5654      * is {@link InvokeOp.InvokeKind#STATIC instance}.
5655      * <p>
5656      * The invoke return type is the invoke descriptors return type.
5657      *
5658      * @param invokeDescriptor the invoke descriptor
5659      * @param args             the invoke arguments
5660      * @return the invoke operation
5661      */
5662     public static InvokeOp invoke(MethodRef invokeDescriptor, List<Value> args) {
5663         return invoke(invokeDescriptor.type().returnType(), invokeDescriptor, args);
5664     }
5665 
5666     /**
5667      * Creates an invoke operation modeling an invocation to an
5668      * instance or static (class) method with no variable arguments.
5669      * <p>
5670      * The invoke kind of the invoke operation is determined by
5671      * comparing the argument count with the invoke descriptor's
5672      * parameter count. If they are equal then the invoke kind is
5673      * {@link InvokeOp.InvokeKind#STATIC static}. If the parameter count
5674      * plus one is equal to the argument count then the invoke kind
5675      * is {@link InvokeOp.InvokeKind#STATIC instance}.
5676      *
5677      * @param returnType       the invoke return type
5678      * @param invokeDescriptor the invoke descriptor
5679      * @param args             the invoke arguments
5680      * @return the invoke operation
5681      */
5682     public static InvokeOp invoke(TypeElement returnType, MethodRef invokeDescriptor, Value... args) {
5683         return invoke(returnType, invokeDescriptor, List.of(args));
5684     }
5685 
5686     /**
5687      * Creates an invoke operation modeling an invocation to an
5688      * instance or static (class) method with no variable arguments.
5689      * <p>
5690      * The invoke kind of the invoke operation is determined by
5691      * comparing the argument count with the invoke descriptor's
5692      * parameter count. If they are equal then the invoke kind is
5693      * {@link InvokeOp.InvokeKind#STATIC static}. If the parameter count
5694      * plus one is equal to the argument count then the invoke kind
5695      * is {@link InvokeOp.InvokeKind#STATIC instance}.
5696      *
5697      * @param returnType       the invoke return type
5698      * @param invokeDescriptor the invoke descriptor
5699      * @param args             the invoke arguments
5700      * @return the invoke super operation
5701      */
5702     public static InvokeOp invoke(TypeElement returnType, MethodRef invokeDescriptor, List<Value> args) {
5703         int paramCount = invokeDescriptor.type().parameterTypes().size();
5704         int argCount = args.size();
5705         InvokeOp.InvokeKind ik = (argCount == paramCount + 1)
5706                 ? InvokeOp.InvokeKind.INSTANCE
5707                 : InvokeOp.InvokeKind.STATIC;
5708         return new InvokeOp(ik, false, returnType, invokeDescriptor, args);
5709     }
5710 
5711     /**
5712      * Creates an invoke operation modelling an invocation to a method.
5713      *
5714      * @param invokeKind       the invoke kind
5715      * @param isVarArgs        true if an invocation to a variable argument method
5716      * @param returnType       the return type
5717      * @param invokeDescriptor the invoke descriptor
5718      * @param args             the invoke arguments
5719      * @return the invoke operation
5720      * @throws IllegalArgumentException if there is a mismatch between the argument count
5721      *                                  and the invoke descriptors parameter count.
5722      */
5723     public static InvokeOp invoke(InvokeOp.InvokeKind invokeKind, boolean isVarArgs,
5724                                   TypeElement returnType, MethodRef invokeDescriptor, Value... args) {
5725         return new InvokeOp(invokeKind, isVarArgs, returnType, invokeDescriptor, List.of(args));
5726     }
5727 
5728     /**
5729      * Creates an invoke operation modelling an invocation to a method.
5730      *
5731      * @param invokeKind       the invoke kind
5732      * @param isVarArgs        true if an invocation to a variable argument method
5733      * @param returnType       the return type
5734      * @param invokeDescriptor the invoke descriptor
5735      * @param args             the invoke arguments
5736      * @return the invoke operation
5737      * @throws IllegalArgumentException if there is a mismatch between the argument count
5738      *                                  and the invoke descriptors parameter count.
5739      */
5740     public static InvokeOp invoke(InvokeOp.InvokeKind invokeKind, boolean isVarArgs,
5741                                   TypeElement returnType, MethodRef invokeDescriptor, List<Value> args) {
5742         return new InvokeOp(invokeKind, isVarArgs, returnType, invokeDescriptor, args);
5743     }
5744 
5745     /**
5746      * Creates a conversion operation.
5747      *
5748      * @param to   the conversion target type
5749      * @param from the value to be converted
5750      * @return the conversion operation
5751      */
5752     public static ConvOp conv(TypeElement to, Value from) {
5753         return new ConvOp(to, from);
5754     }
5755 
5756     /**
5757      * Creates an instance creation operation.
5758      *
5759      * @param constructorDescriptor the constructor descriptor
5760      * @param args            the constructor arguments
5761      * @return the instance creation operation
5762      */
5763     public static NewOp new_(MethodRef constructorDescriptor, Value... args) {
5764         return new_(constructorDescriptor, List.of(args));
5765     }
5766 
5767     /**
5768      * Creates an instance creation operation.
5769      *
5770      * @param constructorDescriptor the constructor descriptor
5771      * @param args            the constructor arguments
5772      * @return the instance creation operation
5773      */
5774     public static NewOp new_(MethodRef constructorDescriptor, List<Value> args) {
5775         return new NewOp(false, constructorDescriptor.refType(), constructorDescriptor, args);
5776     }
5777 
5778     /**
5779      * Creates an instance creation operation.
5780      *
5781      * @param returnType      the instance type
5782      * @param constructorDescriptor the constructor descriptor
5783      * @param args            the constructor arguments
5784      * @return the instance creation operation
5785      */
5786     public static NewOp new_(TypeElement returnType, MethodRef constructorDescriptor,
5787                              Value... args) {
5788         return new_(returnType, constructorDescriptor, List.of(args));
5789     }
5790 
5791     /**
5792      * Creates an instance creation operation.
5793      *
5794      * @param returnType      the instance type
5795      * @param constructorDescriptor the constructor descriptor
5796      * @param args            the constructor arguments
5797      * @return the instance creation operation
5798      */
5799     public static NewOp new_(TypeElement returnType, MethodRef constructorDescriptor,
5800                              List<Value> args) {
5801         return new NewOp(false, returnType, constructorDescriptor, args);
5802     }
5803 
5804     /**
5805      * Creates an instance creation operation.
5806      *
5807      * @param isVarargs {@code true} if calling a varargs constructor
5808      * @param returnType      the instance type
5809      * @param constructorDescriptor the constructor descriptor
5810      * @param args            the constructor arguments
5811      * @return the instance creation operation
5812      */
5813     public static NewOp new_(boolean isVarargs, TypeElement returnType, MethodRef constructorDescriptor,
5814                              List<Value> args) {
5815         return new NewOp(isVarargs, returnType, constructorDescriptor, args);
5816     }
5817 
5818     /**
5819      * Creates an array creation operation.
5820      *
5821      * @param arrayType the array type
5822      * @param length    the array size
5823      * @return the array creation operation
5824      */
5825     public static NewOp newArray(TypeElement arrayType, Value length) {
5826         MethodRef constructorDescriptor = MethodRef.constructor(arrayType, INT);
5827         return new_(constructorDescriptor, length);
5828     }
5829 
5830     /**
5831      * Creates a field load operation to a non-static field.
5832      *
5833      * @param descriptor the field descriptor
5834      * @param receiver   the receiver value
5835      * @return the field load operation
5836      */
5837     public static FieldAccessOp.FieldLoadOp fieldLoad(FieldRef descriptor, Value receiver) {
5838         return new FieldAccessOp.FieldLoadOp(descriptor.type(), descriptor, receiver);
5839     }
5840 
5841     /**
5842      * Creates a field load operation to a non-static field.
5843      *
5844      * @param resultType the result type of the operation
5845      * @param descriptor the field descriptor
5846      * @param receiver   the receiver value
5847      * @return the field load operation
5848      */
5849     public static FieldAccessOp.FieldLoadOp fieldLoad(TypeElement resultType, FieldRef descriptor, Value receiver) {
5850         return new FieldAccessOp.FieldLoadOp(resultType, descriptor, receiver);
5851     }
5852 
5853     /**
5854      * Creates a field load operation to a static field.
5855      *
5856      * @param descriptor the field descriptor
5857      * @return the field load operation
5858      */
5859     public static FieldAccessOp.FieldLoadOp fieldLoad(FieldRef descriptor) {
5860         return new FieldAccessOp.FieldLoadOp(descriptor.type(), descriptor);
5861     }
5862 
5863     /**
5864      * Creates a field load operation to a static field.
5865      *
5866      * @param resultType the result type of the operation
5867      * @param descriptor the field descriptor
5868      * @return the field load operation
5869      */
5870     public static FieldAccessOp.FieldLoadOp fieldLoad(TypeElement resultType, FieldRef descriptor) {
5871         return new FieldAccessOp.FieldLoadOp(resultType, descriptor);
5872     }
5873 
5874     /**
5875      * Creates a field store operation to a non-static field.
5876      *
5877      * @param descriptor the field descriptor
5878      * @param receiver   the receiver value
5879      * @param v          the value to store
5880      * @return the field store operation
5881      */
5882     public static FieldAccessOp.FieldStoreOp fieldStore(FieldRef descriptor, Value receiver, Value v) {
5883         return new FieldAccessOp.FieldStoreOp(descriptor, receiver, v);
5884     }
5885 
5886     /**
5887      * Creates a field load operation to a static field.
5888      *
5889      * @param descriptor the field descriptor
5890      * @param v          the value to store
5891      * @return the field store operation
5892      */
5893     public static FieldAccessOp.FieldStoreOp fieldStore(FieldRef descriptor, Value v) {
5894         return new FieldAccessOp.FieldStoreOp(descriptor, v);
5895     }
5896 
5897     /**
5898      * Creates an array length operation.
5899      *
5900      * @param array the array value
5901      * @return the array length operation
5902      */
5903     public static ArrayLengthOp arrayLength(Value array) {
5904         return new ArrayLengthOp(array);
5905     }
5906 
5907     /**
5908      * Creates an array load operation.
5909      *
5910      * @param array the array value
5911      * @param index the index value
5912      * @return the array load operation
5913      */
5914     public static ArrayAccessOp.ArrayLoadOp arrayLoadOp(Value array, Value index) {
5915         return new ArrayAccessOp.ArrayLoadOp(array, index);
5916     }
5917 
5918     /**
5919      * Creates an array load operation.
5920      *
5921      * @param array the array value
5922      * @param index the index value
5923      * @param componentType type of the array component
5924      * @return the array load operation
5925      */
5926     public static ArrayAccessOp.ArrayLoadOp arrayLoadOp(Value array, Value index, TypeElement componentType) {
5927         return new ArrayAccessOp.ArrayLoadOp(array, index, componentType);
5928     }
5929 
5930     /**
5931      * Creates an array store operation.
5932      *
5933      * @param array the array value
5934      * @param index the index value
5935      * @param v     the value to store
5936      * @return the array store operation
5937      */
5938     public static ArrayAccessOp.ArrayStoreOp arrayStoreOp(Value array, Value index, Value v) {
5939         return new ArrayAccessOp.ArrayStoreOp(array, index, v);
5940     }
5941 
5942     /**
5943      * Creates an instanceof operation.
5944      *
5945      * @param t the type to test against
5946      * @param v the value to test
5947      * @return the instanceof operation
5948      */
5949     public static InstanceOfOp instanceOf(TypeElement t, Value v) {
5950         return new InstanceOfOp(t, v);
5951     }
5952 
5953     /**
5954      * Creates a cast operation.
5955      *
5956      * @param resultType the result type of the operation
5957      * @param v          the value to cast
5958      * @return the cast operation
5959      */
5960     public static CastOp cast(TypeElement resultType, Value v) {
5961         return new CastOp(resultType, resultType, v);
5962     }
5963 
5964     /**
5965      * Creates a cast operation.
5966      *
5967      * @param resultType the result type of the operation
5968      * @param t          the type to cast to
5969      * @param v          the value to cast
5970      * @return the cast operation
5971      */
5972     public static CastOp cast(TypeElement resultType, JavaType t, Value v) {
5973         return new CastOp(resultType, t, v);
5974     }
5975 
5976     /**
5977      * Creates an add operation.
5978      *
5979      * @param lhs the first operand
5980      * @param rhs the second operand
5981      * @return the add operation
5982      */
5983     public static AddOp add(Value lhs, Value rhs) {
5984         return new AddOp(lhs, rhs);
5985     }
5986 
5987     /**
5988      * Creates a sub operation.
5989      *
5990      * @param lhs the first operand
5991      * @param rhs the second operand
5992      * @return the sub operation
5993      */
5994     public static SubOp sub(Value lhs, Value rhs) {
5995         return new SubOp(lhs, rhs);
5996     }
5997 
5998     /**
5999      * Creates a mul operation.
6000      *
6001      * @param lhs the first operand
6002      * @param rhs the second operand
6003      * @return the mul operation
6004      */
6005     public static MulOp mul(Value lhs, Value rhs) {
6006         return new MulOp(lhs, rhs);
6007     }
6008 
6009     /**
6010      * Creates a div operation.
6011      *
6012      * @param lhs the first operand
6013      * @param rhs the second operand
6014      * @return the div operation
6015      */
6016     public static DivOp div(Value lhs, Value rhs) {
6017         return new DivOp(lhs, rhs);
6018     }
6019 
6020     /**
6021      * Creates a mod operation.
6022      *
6023      * @param lhs the first operand
6024      * @param rhs the second operand
6025      * @return the mod operation
6026      */
6027     public static ModOp mod(Value lhs, Value rhs) {
6028         return new ModOp(lhs, rhs);
6029     }
6030 
6031     /**
6032      * Creates a bitwise/logical or operation.
6033      *
6034      * @param lhs the first operand
6035      * @param rhs the second operand
6036      * @return the or operation
6037      */
6038     public static OrOp or(Value lhs, Value rhs) {
6039         return new OrOp(lhs, rhs);
6040     }
6041 
6042     /**
6043      * Creates a bitwise/logical and operation.
6044      *
6045      * @param lhs the first operand
6046      * @param rhs the second operand
6047      * @return the and operation
6048      */
6049     public static AndOp and(Value lhs, Value rhs) {
6050         return new AndOp(lhs, rhs);
6051     }
6052 
6053     /**
6054      * Creates a bitwise/logical xor operation.
6055      *
6056      * @param lhs the first operand
6057      * @param rhs the second operand
6058      * @return the xor operation
6059      */
6060     public static XorOp xor(Value lhs, Value rhs) {
6061         return new XorOp(lhs, rhs);
6062     }
6063 
6064     /**
6065      * Creates a left shift operation.
6066      *
6067      * @param lhs the first operand
6068      * @param rhs the second operand
6069      * @return the xor operation
6070      */
6071     public static LshlOp lshl(Value lhs, Value rhs) {
6072         return new LshlOp(lhs, rhs);
6073     }
6074 
6075     /**
6076      * Creates a right shift operation.
6077      *
6078      * @param lhs the first operand
6079      * @param rhs the second operand
6080      * @return the xor operation
6081      */
6082     public static AshrOp ashr(Value lhs, Value rhs) {
6083         return new AshrOp(lhs, rhs);
6084     }
6085 
6086     /**
6087      * Creates an unsigned right shift operation.
6088      *
6089      * @param lhs the first operand
6090      * @param rhs the second operand
6091      * @return the xor operation
6092      */
6093     public static LshrOp lshr(Value lhs, Value rhs) {
6094         return new LshrOp(lhs, rhs);
6095     }
6096 
6097     /**
6098      * Creates a neg operation.
6099      *
6100      * @param v the operand
6101      * @return the neg operation
6102      */
6103     public static NegOp neg(Value v) {
6104         return new NegOp(v);
6105     }
6106 
6107     /**
6108      * Creates a bitwise complement operation.
6109      *
6110      * @param v the operand
6111      * @return the bitwise complement operation
6112      */
6113     public static ComplOp compl(Value v) {
6114         return new ComplOp(v);
6115     }
6116 
6117     /**
6118      * Creates a not operation.
6119      *
6120      * @param v the operand
6121      * @return the not operation
6122      */
6123     public static NotOp not(Value v) {
6124         return new NotOp(v);
6125     }
6126 
6127     /**
6128      * Creates an equals comparison operation.
6129      *
6130      * @param lhs the first operand
6131      * @param rhs the second operand
6132      * @return the equals comparison operation
6133      */
6134     public static EqOp eq(Value lhs, Value rhs) {
6135         return new EqOp(lhs, rhs);
6136     }
6137 
6138     /**
6139      * Creates a not equals comparison operation.
6140      *
6141      * @param lhs the first operand
6142      * @param rhs the second operand
6143      * @return the not equals comparison operation
6144      */
6145     public static NeqOp neq(Value lhs, Value rhs) {
6146         return new NeqOp(lhs, rhs);
6147     }
6148 
6149     /**
6150      * Creates a greater than comparison operation.
6151      *
6152      * @param lhs the first operand
6153      * @param rhs the second operand
6154      * @return the greater than comparison operation
6155      */
6156     public static GtOp gt(Value lhs, Value rhs) {
6157         return new GtOp(lhs, rhs);
6158     }
6159 
6160     /**
6161      * Creates a greater than or equals to comparison operation.
6162      *
6163      * @param lhs the first operand
6164      * @param rhs the second operand
6165      * @return the greater than or equals to comparison operation
6166      */
6167     public static GeOp ge(Value lhs, Value rhs) {
6168         return new GeOp(lhs, rhs);
6169     }
6170 
6171     /**
6172      * Creates a less than comparison operation.
6173      *
6174      * @param lhs the first operand
6175      * @param rhs the second operand
6176      * @return the less than comparison operation
6177      */
6178     public static LtOp lt(Value lhs, Value rhs) {
6179         return new LtOp(lhs, rhs);
6180     }
6181 
6182     /**
6183      * Creates a less than or equals to comparison operation.
6184      *
6185      * @param lhs the first operand
6186      * @param rhs the second operand
6187      * @return the less than or equals to comparison operation
6188      */
6189     public static LeOp le(Value lhs, Value rhs) {
6190         return new LeOp(lhs, rhs);
6191     }
6192 
6193     /**
6194      * Creates a string concatenation operation.
6195      *
6196      * @param lhs the first operand
6197      * @param rhs the second operand
6198      * @return the string concatenation operation
6199      */
6200     public static ConcatOp concat(Value lhs, Value rhs) {
6201         return new ConcatOp(lhs, rhs);
6202     }
6203 
6204     /**
6205      * Creates a continue operation.
6206      *
6207      * @return the continue operation
6208      */
6209     public static ContinueOp continue_() {
6210         return continue_(null);
6211     }
6212 
6213     /**
6214      * Creates a continue operation.
6215      *
6216      * @param label the value associated with where to continue from
6217      * @return the continue operation
6218      */
6219     public static ContinueOp continue_(Value label) {
6220         return new ContinueOp(label);
6221     }
6222 
6223     /**
6224      * Creates a break operation.
6225      *
6226      * @return the break operation
6227      */
6228     public static BreakOp break_() {
6229         return break_(null);
6230     }
6231 
6232     /**
6233      * Creates a break operation.
6234      *
6235      * @param label the value associated with where to continue from
6236      * @return the break operation
6237      */
6238     public static BreakOp break_(Value label) {
6239         return new BreakOp(label);
6240     }
6241 
6242     /**
6243      * Creates a yield operation.
6244      *
6245      * @return the yield operation
6246      */
6247     public static YieldOp java_yield() {
6248         return java_yield(null);
6249     }
6250 
6251     /**
6252      * Creates a yield operation.
6253      *
6254      * @param operand the value to yield
6255      * @return the yield operation
6256      */
6257     public static YieldOp java_yield(Value operand) {
6258         return new YieldOp(operand);
6259     }
6260 
6261     /**
6262      * Creates a block operation.
6263      *
6264      * @param body the body builder of the operation to be built and become its child
6265      * @return the block operation
6266      */
6267     public static BlockOp block(Body.Builder body) {
6268         return new BlockOp(body);
6269     }
6270 
6271     /**
6272      * Creates a synchronized operation.
6273      *
6274      * @param expr the expression body builder of the operation to be built and become its child
6275      * @param blockBody the block body builder of the operation to be built and become its child
6276      * @return the synchronized operation
6277      */
6278     public static SynchronizedOp synchronized_(Body.Builder expr, Body.Builder blockBody) {
6279         return new SynchronizedOp(expr, blockBody);
6280     }
6281 
6282     /**
6283      * Creates a labeled operation.
6284      *
6285      * @param body the body builder of the operation to be built and become its child
6286      * @return the block operation
6287      */
6288     public static LabeledOp labeled(Body.Builder body) {
6289         return new LabeledOp(body);
6290     }
6291 
6292     /**
6293      * Creates an if operation builder.
6294      *
6295      * @param ancestorBody the nearest ancestor body builder from which to construct
6296      *                     body builders for this operation
6297      * @return the if operation builder
6298      */
6299     public static IfOp.IfBuilder if_(Body.Builder ancestorBody) {
6300         return new IfOp.IfBuilder(ancestorBody);
6301     }
6302 
6303     // Pairs of
6304     //   predicate ()boolean, body ()void
6305     // And one optional body ()void at the end
6306 
6307     /**
6308      * Creates an if operation.
6309      *
6310      * @param bodies the body builders of operation to be built and become its children
6311      * @return the if operation
6312      */
6313     public static IfOp if_(List<Body.Builder> bodies) {
6314         return new IfOp(bodies);
6315     }
6316 
6317     /**
6318      * Creates a switch expression operation.
6319      * <p>
6320      * The result type of the operation will be derived from the yield type of the second body
6321      *
6322      * @param target the switch target value
6323      * @param bodies the body builders of the operation to be built and become its children
6324      * @return the switch expression operation
6325      */
6326     public static SwitchExpressionOp switchExpression(Value target, List<Body.Builder> bodies) {
6327         return new SwitchExpressionOp(null, target, bodies);
6328     }
6329 
6330     /**
6331      * Creates a switch expression operation.
6332      *
6333      * @param resultType the result type of the expression
6334      * @param target     the switch target value
6335      * @param bodies     the body builders of the operation to be built and become its children
6336      * @return the switch expression operation
6337      */
6338     public static SwitchExpressionOp switchExpression(TypeElement resultType, Value target,
6339                                                       List<Body.Builder> bodies) {
6340         Objects.requireNonNull(resultType);
6341         return new SwitchExpressionOp(resultType, target, bodies);
6342     }
6343 
6344     /**
6345      * Creates a switch statement operation.
6346      * @param target the switch target value
6347      * @param bodies the body builders of the operation to be built and become its children
6348      * @return the switch statement operation
6349      */
6350     public static SwitchStatementOp switchStatement(Value target, List<Body.Builder> bodies) {
6351         return new SwitchStatementOp(target, bodies);
6352     }
6353 
6354     /**
6355      * Creates a switch fallthrough operation.
6356      *
6357      * @return the switch fallthrough operation
6358      */
6359     public static SwitchFallthroughOp switchFallthroughOp() {
6360         return new SwitchFallthroughOp();
6361     }
6362 
6363     /**
6364      * Creates a for operation builder.
6365      *
6366      * @param ancestorBody the nearest ancestor body builder from which to construct
6367      *                     body builders for this operation
6368      * @param initTypes    the types of initialized variables
6369      * @return the for operation builder
6370      */
6371     public static ForOp.InitBuilder for_(Body.Builder ancestorBody, TypeElement... initTypes) {
6372         return for_(ancestorBody, List.of(initTypes));
6373     }
6374 
6375     /**
6376      * Creates a for operation builder.
6377      *
6378      * @param ancestorBody the nearest ancestor body builder from which to construct
6379      *                     body builders for this operation
6380      * @param initTypes    the types of initialized variables
6381      * @return the for operation builder
6382      */
6383     public static ForOp.InitBuilder for_(Body.Builder ancestorBody, List<? extends TypeElement> initTypes) {
6384         return new ForOp.InitBuilder(ancestorBody, initTypes);
6385     }
6386 
6387 
6388     /**
6389      * Creates a for operation.
6390      *
6391      * @param init   the init body builder of the operation to be built and become its child
6392      * @param cond   the cond body builder of the operation to be built and become its child
6393      * @param update the update body builder of the operation to be built and become its child
6394      * @param body   the main body builder of the operation to be built and become its child
6395      * @return the for operation
6396      */
6397     // init ()Tuple<Var<T1>, Var<T2>, ..., Var<TN>>, or init ()void
6398     // cond (Var<T1>, Var<T2>, ..., Var<TN>)boolean
6399     // update (Var<T1>, Var<T2>, ..., Var<TN>)void
6400     // body (Var<T1>, Var<T2>, ..., Var<TN>)void
6401     public static ForOp for_(Body.Builder init,
6402                              Body.Builder cond,
6403                              Body.Builder update,
6404                              Body.Builder body) {
6405         return new ForOp(init, cond, update, body);
6406     }
6407 
6408     /**
6409      * Creates an enhanced for operation builder.
6410      *
6411      * @param ancestorBody the nearest ancestor body builder from which to construct
6412      *                     body builders for this operation
6413      * @param iterableType the iterable type
6414      * @param elementType  the element type
6415      * @return the enhanced for operation builder
6416      */
6417     public static EnhancedForOp.ExpressionBuilder enhancedFor(Body.Builder ancestorBody,
6418                                                               TypeElement iterableType, TypeElement elementType) {
6419         return new EnhancedForOp.ExpressionBuilder(ancestorBody, iterableType, elementType);
6420     }
6421 
6422     // expression ()I<E>
6423     // init (E )Var<T>
6424     // body (Var<T> )void
6425 
6426     /**
6427      * Creates an enhanced for operation.
6428      *
6429      * @param expression the expression body builder of the operation to be built and become its child
6430      * @param init       the init body builder of the operation to be built and become its child
6431      * @param body       the main body builder of the operation to be built and become its child
6432      * @return the enhanced for operation
6433      */
6434     public static EnhancedForOp enhancedFor(Body.Builder expression,
6435                                             Body.Builder init,
6436                                             Body.Builder body) {
6437         return new EnhancedForOp(expression, init, body);
6438     }
6439 
6440     /**
6441      * Creates a while operation builder.
6442      *
6443      * @param ancestorBody the nearest ancestor body builder from which to construct
6444      *                     body builders for this operation
6445      * @return the while operation builder
6446      */
6447     public static WhileOp.PredicateBuilder while_(Body.Builder ancestorBody) {
6448         return new WhileOp.PredicateBuilder(ancestorBody);
6449     }
6450 
6451     /**
6452      * Creates a while operation.
6453      *
6454      * @param predicate the predicate body builder of the operation to be built and become its child
6455      * @param body      the main body builder of the operation to be built and become its child
6456      * @return the while operation
6457      */
6458     // predicate, ()boolean, may be null for predicate returning true
6459     // body, ()void
6460     public static WhileOp while_(Body.Builder predicate, Body.Builder body) {
6461         return new WhileOp(predicate, body);
6462     }
6463 
6464     /**
6465      * Creates a do operation builder.
6466      *
6467      * @param ancestorBody the nearest ancestor body builder from which to construct
6468      *                     body builders for this operation
6469      * @return the do operation builder
6470      */
6471     public static DoWhileOp.BodyBuilder doWhile(Body.Builder ancestorBody) {
6472         return new DoWhileOp.BodyBuilder(ancestorBody);
6473     }
6474 
6475     /**
6476      * Creates a do operation.
6477      *
6478      * @param predicate the predicate body builder of the operation to be built and become its child
6479      * @param body      the main body builder of the operation to be built and become its child
6480      * @return the do operation
6481      */
6482     public static DoWhileOp doWhile(Body.Builder body, Body.Builder predicate) {
6483         return new DoWhileOp(body, predicate);
6484     }
6485 
6486     /**
6487      * Creates a conditional-and operation builder.
6488      *
6489      * @param ancestorBody the nearest ancestor body builder from which to construct
6490      *                     body builders for this operation
6491      * @param lhs          a consumer that builds the left-hand side body
6492      * @param rhs          a consumer that builds the right-hand side body
6493      * @return the conditional-and operation builder
6494      */
6495     public static ConditionalAndOp.Builder conditionalAnd(Body.Builder ancestorBody,
6496                                                           Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
6497         return new ConditionalAndOp.Builder(ancestorBody, lhs, rhs);
6498     }
6499 
6500     /**
6501      * Creates a conditional-or operation builder.
6502      *
6503      * @param ancestorBody the nearest ancestor body builder from which to construct
6504      *                     body builders for this operation
6505      * @param lhs          a consumer that builds the left-hand side body
6506      * @param rhs          a consumer that builds the right-hand side body
6507      * @return the conditional-or operation builder
6508      */
6509     public static ConditionalOrOp.Builder conditionalOr(Body.Builder ancestorBody,
6510                                                         Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
6511         return new ConditionalOrOp.Builder(ancestorBody, lhs, rhs);
6512     }
6513 
6514     /**
6515      * Creates a conditional-and operation
6516      *
6517      * @param bodies the body builders of operation to be built and become its children
6518      * @return the conditional-and operation
6519      */
6520     // predicates, ()boolean
6521     public static ConditionalAndOp conditionalAnd(List<Body.Builder> bodies) {
6522         return new ConditionalAndOp(bodies);
6523     }
6524 
6525     /**
6526      * Creates a conditional-or operation
6527      *
6528      * @param bodies the body builders of operation to be built and become its children
6529      * @return the conditional-or operation
6530      */
6531     // predicates, ()boolean
6532     public static ConditionalOrOp conditionalOr(List<Body.Builder> bodies) {
6533         return new ConditionalOrOp(bodies);
6534     }
6535 
6536     /**
6537      * Creates a conditional operation
6538      *
6539      * @param expressionType the result type of the expression
6540      * @param bodies         the body builders of operation to be built and become its children
6541      * @return the conditional operation
6542      */
6543     public static ConditionalExpressionOp conditionalExpression(TypeElement expressionType,
6544                                                                 List<Body.Builder> bodies) {
6545         Objects.requireNonNull(expressionType);
6546         return new ConditionalExpressionOp(expressionType, bodies);
6547     }
6548 
6549     /**
6550      * Creates a conditional operation
6551      * <p>
6552      * The result type of the operation will be derived from the yield type of the second body
6553      *
6554      * @param bodies the body builders of operation to be built and become its children
6555      * @return the conditional operation
6556      */
6557     public static ConditionalExpressionOp conditionalExpression(List<Body.Builder> bodies) {
6558         return new ConditionalExpressionOp(null, bodies);
6559     }
6560 
6561     /**
6562      * Creates try operation builder.
6563      *
6564      * @param ancestorBody the nearest ancestor body builder from which to construct
6565      *                     body builders for this operation
6566      * @param c            a consumer that builds the try body
6567      * @return the try operation builder
6568      */
6569     public static TryOp.CatchBuilder try_(Body.Builder ancestorBody, Consumer<Block.Builder> c) {
6570         Body.Builder _try = Body.Builder.of(ancestorBody, CoreType.FUNCTION_TYPE_VOID);
6571         c.accept(_try.entryBlock());
6572         return new TryOp.CatchBuilder(ancestorBody, null, _try);
6573     }
6574 
6575     /**
6576      * Creates try-with-resources operation builder.
6577      *
6578      * @param ancestorBody the nearest ancestor body builder from which to construct
6579      *                     body builders for this operation
6580      * @param resourceTypes the resource types used in the try-with-resources construct
6581      * @param c            a consumer that builds the resources body
6582      * @return the try-with-resources operation builder
6583      */
6584     public static TryOp.BodyBuilder tryWithResources(Body.Builder ancestorBody,
6585                                                      List<? extends TypeElement> resourceTypes,
6586                                                      Consumer<Block.Builder> c) {
6587         resourceTypes = resourceTypes.stream().map(CoreType::varType).toList();
6588         Body.Builder resources = Body.Builder.of(ancestorBody,
6589                 CoreType.functionType(CoreType.tupleType(resourceTypes)));
6590         c.accept(resources.entryBlock());
6591         return new TryOp.BodyBuilder(ancestorBody, resourceTypes, resources);
6592     }
6593 
6594     // resources ()Tuple<Var<R1>, Var<R2>, ..., Var<RN>>, or null
6595     // try (Var<R1>, Var<R2>, ..., Var<RN>)void, or try ()void
6596     // catch (E )void, where E <: Throwable
6597     // finally ()void, or null
6598 
6599     /**
6600      * Creates a try or try-with-resources operation.
6601      *
6602      * @param resources the try body builder of the operation to be built and become its child,
6603      *                  may be null
6604      * @param body      the try body builder of the operation to be built and become its child
6605      * @param catchers  the catch body builders of the operation to be built and become its children
6606      * @param finalizer the finalizer body builder of the operation to be built and become its child
6607      * @return the try or try-with-resources operation
6608      */
6609     public static TryOp try_(Body.Builder resources,
6610                              Body.Builder body,
6611                              List<Body.Builder> catchers,
6612                              Body.Builder finalizer) {
6613         return new TryOp(resources, body, catchers, finalizer);
6614     }
6615 
6616     //
6617     // Patterns
6618 
6619     /**
6620      * Creates a pattern match operation.
6621      *
6622      * @param target  the target value
6623      * @param pattern the pattern body builder of the operation to be built and become its child
6624      * @param match   the match body builder of the operation to be built and become its child
6625      * @return the pattern match operation
6626      */
6627     public static PatternOps.MatchOp match(Value target,
6628                                            Body.Builder pattern, Body.Builder match) {
6629         return new PatternOps.MatchOp(target, pattern, match);
6630     }
6631 
6632     /**
6633      * Creates a pattern binding operation.
6634      *
6635      * @param type        the type of value to be bound
6636      * @param bindingName the binding name
6637      * @return the pattern binding operation
6638      */
6639     public static PatternOps.TypePatternOp typePattern(TypeElement type, String bindingName) {
6640         return new PatternOps.TypePatternOp(type, bindingName);
6641     }
6642 
6643     /**
6644      * Creates a record pattern operation.
6645      *
6646      * @param recordDescriptor the record descriptor
6647      * @param nestedPatterns   the nested pattern values
6648      * @return the record pattern operation
6649      */
6650     public static PatternOps.RecordPatternOp recordPattern(RecordTypeRef recordDescriptor, Value... nestedPatterns) {
6651         return recordPattern(recordDescriptor, List.of(nestedPatterns));
6652     }
6653 
6654     /**
6655      * Creates a record pattern operation.
6656      *
6657      * @param recordDescriptor the record descriptor
6658      * @param nestedPatterns   the nested pattern values
6659      * @return the record pattern operation
6660      */
6661     public static PatternOps.RecordPatternOp recordPattern(RecordTypeRef recordDescriptor, List<Value> nestedPatterns) {
6662         return new PatternOps.RecordPatternOp(recordDescriptor, nestedPatterns);
6663     }
6664 
6665     /**
6666      * Creates a match-all pattern operation.
6667      * @return a match-all pattern
6668      */
6669     public static PatternOps.MatchAllPatternOp matchAllPattern() {
6670         return new PatternOps.MatchAllPatternOp();
6671     }
6672 }