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