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