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