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             ConditionalExpressionOp,
  96             JavaConditionalOp,
  97             SwitchExpressionOp {
  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             BlockOp,
 114             DoWhileOp,
 115             EnhancedForOp,
 116             ForOp,
 117             IfOp,
 118             JavaLabelOp,
 119             LabeledOp,
 120             SynchronizedOp,
 121             TryOp,
 122             WhileOp,
 123             YieldOp,
 124             SwitchStatementOp {
 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.rebind(b.context(), OpTransformer.LOWERING_TRANSFORMER).op(this);
 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.ancestorOp();
2008                 if (op == null) {
2009                     throw new IllegalStateException("No enclosing loop");
2010                 }
2011             } while (!(op instanceof Op.Loop || op instanceof SwitchStatementOp));
2012 
2013             return switch (op) {
2014                 case Op.Loop lop -> lop.loopBody() == b ? op : null;
2015                 case SwitchStatementOp 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().ancestorOp() instanceof LabeledOp 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(BreakOp.NAME)
2060     public static final class BreakOp extends JavaLabelOp {
2061         static final String NAME = "java.break";
2062 
2063         BreakOp(ExternalizedOp def) {
2064             this(def.operands().isEmpty() ? null : def.operands().get(0));
2065         }
2066 
2067         BreakOp(BreakOp that, CopyContext cc) {
2068             super(that, cc);
2069         }
2070 
2071         @Override
2072         public BreakOp transform(CopyContext cc, OpTransformer ot) {
2073             return new BreakOp(this, cc);
2074         }
2075 
2076         BreakOp(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(ContinueOp.NAME)
2090     public static final class ContinueOp extends JavaLabelOp {
2091         static final String NAME = "java.continue";
2092 
2093         ContinueOp(ExternalizedOp def) {
2094             this(def.operands().isEmpty() ? null : def.operands().get(0));
2095         }
2096 
2097         ContinueOp(ContinueOp that, CopyContext cc) {
2098             super(that, cc);
2099         }
2100 
2101         @Override
2102         public ContinueOp transform(CopyContext cc, OpTransformer ot) {
2103             return new ContinueOp(this, cc);
2104         }
2105 
2106         ContinueOp(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(YieldOp.NAME)
2141     public static final class YieldOp extends JavaOp
2142             implements Op.BodyTerminating, JavaStatement, Op.Lowerable {
2143         static final String NAME = "java.yield";
2144 
2145         YieldOp(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         YieldOp(YieldOp that, CopyContext cc) {
2154             super(that, cc);
2155         }
2156 
2157         @Override
2158         public YieldOp transform(CopyContext cc, OpTransformer ot) {
2159             return new YieldOp(this, cc);
2160         }
2161 
2162         YieldOp(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.ancestorOp();
2207                 if (op == null) {
2208                     throw new IllegalStateException("No enclosing switch");
2209                 }
2210             } while (!(op instanceof SwitchExpressionOp));
2211             return op;
2212         }
2213     }
2214 
2215     /**
2216      * The block operation, that can model Java language blocks.
2217      */
2218     @OpDeclaration(BlockOp.NAME)
2219     public static final class BlockOp extends JavaOp
2220             implements Op.Nested, Op.Lowerable, JavaStatement {
2221         static final String NAME = "java.block";
2222 
2223         final Body body;
2224 
2225         BlockOp(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         BlockOp(BlockOp 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 BlockOp transform(CopyContext cc, OpTransformer ot) {
2242             return new BlockOp(this, cc, ot);
2243         }
2244 
2245         BlockOp(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 CoreOp.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(SynchronizedOp.NAME)
2298     public static final class SynchronizedOp 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         SynchronizedOp(ExternalizedOp def) {
2306             this(def.bodyDefinitions().get(0), def.bodyDefinitions().get(1));
2307         }
2308 
2309         SynchronizedOp(SynchronizedOp 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 SynchronizedOp transform(CopyContext cc, OpTransformer ot) {
2319             return new SynchronizedOp(this, cc, ot);
2320         }
2321 
2322         SynchronizedOp(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 CoreOp.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 CoreOp.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 || target.isAncestorOf(this);
2444         }
2445 
2446         @Override
2447         public TypeElement resultType() {
2448             return VOID;
2449         }
2450     }
2451 
2452     /**
2453      * The labeled operation, that can model Java language labeled statements.
2454      */
2455     @OpDeclaration(LabeledOp.NAME)
2456     public static final class LabeledOp extends JavaOp
2457             implements Op.Nested, Op.Lowerable, JavaStatement {
2458         static final String NAME = "java.labeled";
2459 
2460         final Body body;
2461 
2462         LabeledOp(ExternalizedOp def) {
2463             if (!def.operands().isEmpty()) {
2464                 throw new IllegalStateException("Operation must have no operands");
2465             }
2466 
2467             this(def.bodyDefinitions().get(0));
2468         }
2469 
2470         LabeledOp(LabeledOp that, CopyContext cc, OpTransformer ot) {
2471             super(that, cc);
2472 
2473             // Copy body
2474             this.body = that.body.transform(cc, ot).build(this);
2475         }
2476 
2477         @Override
2478         public LabeledOp transform(CopyContext cc, OpTransformer ot) {
2479             return new LabeledOp(this, cc, ot);
2480         }
2481 
2482         LabeledOp(Body.Builder bodyC) {
2483             super(NAME, List.of());
2484 
2485             this.body = bodyC.build(this);
2486             if (!body.bodyType().returnType().equals(VOID)) {
2487                 throw new IllegalArgumentException("Body should return void: " + body.bodyType());
2488             }
2489             if (!body.bodyType().parameterTypes().isEmpty()) {
2490                 throw new IllegalArgumentException("Body should have zero parameters: " + body.bodyType());
2491             }
2492         }
2493 
2494         @Override
2495         public List<Body> bodies() {
2496             return List.of(body);
2497         }
2498 
2499         public Op label() {
2500             return body.entryBlock().firstOp();
2501         }
2502 
2503         public Op target() {
2504             return body.entryBlock().nextOp(label());
2505         }
2506 
2507         @Override
2508         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
2509             Block.Builder exit = b.block();
2510             setBranchTarget(b.context(), this, new BranchTarget(exit, null));
2511 
2512             AtomicBoolean first = new AtomicBoolean();
2513             b.transformBody(body, List.of(), opT.andThen((block, op) -> {
2514                 // Drop first operation that corresponds to the label
2515                 if (!first.get()) {
2516                     first.set(true);
2517                     return block;
2518                 }
2519 
2520                 if (op instanceof CoreOp.YieldOp) {
2521                     block.op(branch(exit.successor()));
2522                 } else {
2523                     // @@@ Composition of lowerable ops
2524                     if (op instanceof Lowerable lop) {
2525                         block = lop.lower(block, opT);
2526                     } else {
2527                         block.op(op);
2528                     }
2529                 }
2530                 return block;
2531             }));
2532 
2533             return exit;
2534         }
2535 
2536         @Override
2537         public TypeElement resultType() {
2538             return VOID;
2539         }
2540     }
2541 
2542     /**
2543      * The if operation, that can model Java language if, if-then, and if-then-else statements.
2544      */
2545     @OpDeclaration(IfOp.NAME)
2546     public static final class IfOp extends JavaOp
2547             implements Op.Nested, Op.Lowerable, JavaStatement {
2548 
2549         static final FunctionType PREDICATE_TYPE = CoreType.functionType(BOOLEAN);
2550 
2551         static final FunctionType ACTION_TYPE = CoreType.FUNCTION_TYPE_VOID;
2552 
2553         public static class IfBuilder {
2554             final Body.Builder ancestorBody;
2555             final List<Body.Builder> bodies;
2556 
2557             IfBuilder(Body.Builder ancestorBody) {
2558                 this.ancestorBody = ancestorBody;
2559                 this.bodies = new ArrayList<>();
2560             }
2561 
2562             public ThenBuilder if_(Consumer<Block.Builder> c) {
2563                 Body.Builder body = Body.Builder.of(ancestorBody, PREDICATE_TYPE);
2564                 c.accept(body.entryBlock());
2565                 bodies.add(body);
2566 
2567                 return new ThenBuilder(ancestorBody, bodies);
2568             }
2569         }
2570 
2571         public static class ThenBuilder {
2572             final Body.Builder ancestorBody;
2573             final List<Body.Builder> bodies;
2574 
2575             public ThenBuilder(Body.Builder ancestorBody, List<Body.Builder> bodies) {
2576                 this.ancestorBody = ancestorBody;
2577                 this.bodies = bodies;
2578             }
2579 
2580             public ElseIfBuilder then(Consumer<Block.Builder> c) {
2581                 Body.Builder body = Body.Builder.of(ancestorBody, ACTION_TYPE);
2582                 c.accept(body.entryBlock());
2583                 bodies.add(body);
2584 
2585                 return new ElseIfBuilder(ancestorBody, bodies);
2586             }
2587 
2588             public ElseIfBuilder then() {
2589                 Body.Builder body = Body.Builder.of(ancestorBody, ACTION_TYPE);
2590                 body.entryBlock().op(core_yield());
2591                 bodies.add(body);
2592 
2593                 return new ElseIfBuilder(ancestorBody, bodies);
2594             }
2595         }
2596 
2597         public static class ElseIfBuilder {
2598             final Body.Builder ancestorBody;
2599             final List<Body.Builder> bodies;
2600 
2601             public ElseIfBuilder(Body.Builder ancestorBody, List<Body.Builder> bodies) {
2602                 this.ancestorBody = ancestorBody;
2603                 this.bodies = bodies;
2604             }
2605 
2606             public ThenBuilder elseif(Consumer<Block.Builder> c) {
2607                 Body.Builder body = Body.Builder.of(ancestorBody, PREDICATE_TYPE);
2608                 c.accept(body.entryBlock());
2609                 bodies.add(body);
2610 
2611                 return new ThenBuilder(ancestorBody, bodies);
2612             }
2613 
2614             public IfOp else_(Consumer<Block.Builder> c) {
2615                 Body.Builder body = Body.Builder.of(ancestorBody, ACTION_TYPE);
2616                 c.accept(body.entryBlock());
2617                 bodies.add(body);
2618 
2619                 return new IfOp(bodies);
2620             }
2621 
2622             public IfOp else_() {
2623                 Body.Builder body = Body.Builder.of(ancestorBody, ACTION_TYPE);
2624                 body.entryBlock().op(core_yield());
2625                 bodies.add(body);
2626 
2627                 return new IfOp(bodies);
2628             }
2629         }
2630 
2631         static final String NAME = "java.if";
2632 
2633         final List<Body> bodies;
2634 
2635         IfOp(ExternalizedOp def) {
2636             if (!def.operands().isEmpty()) {
2637                 throw new IllegalStateException("Operation must have no operands");
2638             }
2639 
2640             this(def.bodyDefinitions());
2641         }
2642 
2643         IfOp(IfOp that, CopyContext cc, OpTransformer ot) {
2644             super(that, cc);
2645 
2646             // Copy body
2647             this.bodies = that.bodies.stream()
2648                     .map(b -> b.transform(cc, ot).build(this)).toList();
2649         }
2650 
2651         @Override
2652         public IfOp transform(CopyContext cc, OpTransformer ot) {
2653             return new IfOp(this, cc, ot);
2654         }
2655 
2656         IfOp(List<Body.Builder> bodyCs) {
2657             super(NAME, List.of());
2658 
2659             // Normalize by adding an empty else action
2660             // @@@ Is this needed?
2661             if (bodyCs.size() % 2 == 0) {
2662                 bodyCs = new ArrayList<>(bodyCs);
2663                 Body.Builder end = Body.Builder.of(bodyCs.get(0).ancestorBody(),
2664                         CoreType.FUNCTION_TYPE_VOID);
2665                 end.entryBlock().op(core_yield());
2666                 bodyCs.add(end);
2667             }
2668 
2669             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
2670 
2671             if (bodies.size() < 2) {
2672                 throw new IllegalArgumentException("Incorrect number of bodies: " + bodies.size());
2673             }
2674             for (int i = 0; i < bodies.size(); i += 2) {
2675                 Body action;
2676                 if (i == bodies.size() - 1) {
2677                     action = bodies.get(i);
2678                 } else {
2679                     action = bodies.get(i + 1);
2680                     Body fromPred = bodies.get(i);
2681                     if (!fromPred.bodyType().equals(CoreType.functionType(BOOLEAN))) {
2682                         throw new IllegalArgumentException("Illegal predicate body descriptor: " + fromPred.bodyType());
2683                     }
2684                 }
2685                 if (!action.bodyType().equals(CoreType.FUNCTION_TYPE_VOID)) {
2686                     throw new IllegalArgumentException("Illegal action body descriptor: " + action.bodyType());
2687                 }
2688             }
2689         }
2690 
2691         @Override
2692         public List<Body> bodies() {
2693             return bodies;
2694         }
2695 
2696         @Override
2697         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
2698             Block.Builder exit = b.block();
2699             setBranchTarget(b.context(), this, new BranchTarget(exit, null));
2700 
2701             // Create predicate and action blocks
2702             List<Block.Builder> builders = new ArrayList<>();
2703             for (int i = 0; i < bodies.size(); i += 2) {
2704                 if (i == bodies.size() - 1) {
2705                     builders.add(b.block());
2706                 } else {
2707                     builders.add(i == 0 ? b : b.block());
2708                     builders.add(b.block());
2709                 }
2710             }
2711 
2712             for (int i = 0; i < bodies.size(); i += 2) {
2713                 Body actionBody;
2714                 Block.Builder action;
2715                 if (i == bodies.size() - 1) {
2716                     actionBody = bodies.get(i);
2717                     action = builders.get(i);
2718                 } else {
2719                     Body predBody = bodies.get(i);
2720                     actionBody = bodies.get(i + 1);
2721 
2722                     Block.Builder pred = builders.get(i);
2723                     action = builders.get(i + 1);
2724                     Block.Builder next = builders.get(i + 2);
2725 
2726                     pred.transformBody(predBody, List.of(), opT.andThen((block, op) -> {
2727                         if (op instanceof CoreOp.YieldOp yo) {
2728                             block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
2729                                     action.successor(), next.successor()));
2730                         } else if (op instanceof Lowerable lop) {
2731                             // @@@ Composition of lowerable ops
2732                             block = lop.lower(block, opT);
2733                         } else {
2734                             block.op(op);
2735                         }
2736                         return block;
2737                     }));
2738                 }
2739 
2740                 action.transformBody(actionBody, List.of(), opT.andThen((block, op) -> {
2741                     if (op instanceof CoreOp.YieldOp) {
2742                         block.op(branch(exit.successor()));
2743                     } else {
2744                         // @@@ Composition of lowerable ops
2745                         if (op instanceof Lowerable lop) {
2746                             block = lop.lower(block, opT);
2747                         } else {
2748                             block.op(op);
2749                         }
2750                     }
2751                     return block;
2752                 }));
2753             }
2754 
2755             return exit;
2756         }
2757 
2758         @Override
2759         public TypeElement resultType() {
2760             return VOID;
2761         }
2762     }
2763 
2764     public abstract static sealed class JavaSwitchOp extends JavaOp implements Op.Nested, Op.Lowerable
2765             permits SwitchStatementOp, SwitchExpressionOp {
2766 
2767         final List<Body> bodies;
2768 
2769         JavaSwitchOp(JavaSwitchOp that, CopyContext cc, OpTransformer ot) {
2770             super(that, cc);
2771 
2772             // Copy body
2773             this.bodies = that.bodies.stream()
2774                     .map(b -> b.transform(cc, ot).build(this)).toList();
2775         }
2776 
2777         JavaSwitchOp(String name, Value target, List<Body.Builder> bodyCs) {
2778             super(name, List.of(target));
2779 
2780             // Each case is modelled as a contiguous pair of bodies
2781             // The first body models the case labels, and the second models the case statements
2782             // The labels body has a parameter whose type is target operand's type and returns a boolean value
2783             // The statements body has no parameters and returns void
2784             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
2785         }
2786 
2787         @Override
2788         public List<Body> bodies() {
2789             return bodies;
2790         }
2791 
2792         @Override
2793         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
2794             Value selectorExpression = b.context().getValue(operands().get(0));
2795 
2796             // @@@ we can add this during model generation
2797             // if no case null, add one that throws NPE
2798             if (!(selectorExpression.type() instanceof PrimitiveType) && !haveNullCase()) {
2799                 Block.Builder throwBlock = b.block();
2800                 throwBlock.op(throw_(
2801                         throwBlock.op(new_(ConstructorRef.constructor(NullPointerException.class)))
2802                 ));
2803 
2804                 Block.Builder continueBlock = b.block();
2805 
2806                 Result p = b.op(invoke(MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class),
2807                         selectorExpression, b.op(constant(J_L_OBJECT, null))));
2808                 b.op(conditionalBranch(p, throwBlock.successor(), continueBlock.successor()));
2809 
2810                 b = continueBlock;
2811             }
2812 
2813             List<Block.Builder> blocks = new ArrayList<>();
2814             for (int i = 0; i < bodies().size(); i++) {
2815                 Block.Builder bb = b.block();
2816                 if (i == 0) {
2817                     bb = b;
2818                 }
2819                 blocks.add(bb);
2820             }
2821 
2822             Block.Builder exit;
2823             if (bodies().isEmpty()) {
2824                 exit = b;
2825             } else {
2826                 exit = b.block(resultType());
2827                 if (this instanceof SwitchExpressionOp) {
2828                     exit.context().mapValue(result(), exit.parameters().get(0));
2829                 }
2830             }
2831 
2832             setBranchTarget(b.context(), this, new BranchTarget(exit, null));
2833             // map statement body to nextExprBlock
2834             // this mapping will be used for lowering SwitchFallThroughOp
2835             for (int i = 1; i < bodies().size() - 2; i+=2) {
2836                 setBranchTarget(b.context(), bodies().get(i), new BranchTarget(null, blocks.get(i + 2)));
2837             }
2838 
2839             for (int i = 0; i < bodies().size(); i++) {
2840                 boolean isLabelBody = i % 2 == 0;
2841                 Block.Builder curr = blocks.get(i);
2842                 if (isLabelBody) {
2843                     Block.Builder statement = blocks.get(i + 1);
2844                     boolean isLastLabel = i == blocks.size() - 2;
2845                     Block.Builder nextLabel = isLastLabel ? null : blocks.get(i + 2);
2846                     curr.transformBody(bodies().get(i), List.of(selectorExpression), opT.andThen((block, op) -> {
2847                         switch (op) {
2848                             case CoreOp.YieldOp _ when isLastLabel && this instanceof SwitchExpressionOp -> {
2849                                 block.op(branch(statement.successor()));
2850                             }
2851                             case CoreOp.YieldOp yop -> block.op(conditionalBranch(
2852                                     block.context().getValue(yop.yieldValue()),
2853                                     statement.successor(),
2854                                     isLastLabel ? exit.successor() : nextLabel.successor()
2855                             ));
2856                             case Lowerable lop -> block = lop.lower(block, opT);
2857                             default -> block.op(op);
2858                         }
2859                         return block;
2860                     }));
2861                 } else { // statement body
2862                     curr.transformBody(bodies().get(i), blocks.get(i).parameters(), opT.andThen((block, op) -> {
2863                         switch (op) {
2864                             case CoreOp.YieldOp _ when this instanceof SwitchStatementOp -> block.op(branch(exit.successor()));
2865                             case CoreOp.YieldOp yop when this instanceof SwitchExpressionOp -> block.op(branch(exit.successor(block.context().getValue(yop.yieldValue()))));
2866                             case Lowerable lop -> block = lop.lower(block, opT);
2867                             default -> block.op(op);
2868                         }
2869                         return block;
2870                     }));
2871                 }
2872             }
2873 
2874             return exit;
2875         }
2876 
2877         boolean haveNullCase() {
2878             /*
2879             case null is modeled like this:
2880             (%4 : T)boolean -> {
2881                 %5 : java.lang.Object = constant @null;
2882                 %6 : boolean = invoke %4 %5 @"java.util.Objects::equals(java.lang.Object, java.lang.Object)boolean";
2883                 yield %6;
2884             }
2885             * */
2886             for (int i = 0; i < bodies().size() - 2; i+=2) {
2887                 Body labelBody = bodies().get(i);
2888                 if (labelBody.blocks().size() != 1) {
2889                     continue; // we skip, for now
2890                 }
2891                 Op terminatingOp = bodies().get(i).entryBlock().terminatingOp();
2892                 //@@@ when op pattern matching is ready, we can use it
2893                 if (terminatingOp instanceof CoreOp.YieldOp yieldOp &&
2894                         yieldOp.yieldValue() instanceof Op.Result opr &&
2895                         opr.op() instanceof InvokeOp invokeOp &&
2896                         invokeOp.invokeDescriptor().equals(MethodRef.method(Objects.class, "equals", boolean.class, Object.class, Object.class)) &&
2897                         invokeOp.operands().stream().anyMatch(o -> o instanceof Op.Result r && r.op() instanceof ConstantOp cop && cop.value() == null)) {
2898                     return true;
2899                 }
2900             }
2901             return false;
2902         }
2903     }
2904 
2905     /**
2906      * The switch expression operation, that can model Java language switch expressions.
2907      */
2908     @OpDeclaration(SwitchExpressionOp.NAME)
2909     public static final class SwitchExpressionOp extends JavaSwitchOp
2910             implements JavaExpression {
2911         static final String NAME = "java.switch.expression";
2912 
2913         final TypeElement resultType;
2914 
2915         SwitchExpressionOp(ExternalizedOp def) {
2916             this(def.resultType(), def.operands().get(0), def.bodyDefinitions());
2917         }
2918 
2919         SwitchExpressionOp(SwitchExpressionOp that, CopyContext cc, OpTransformer ot) {
2920             super(that, cc, ot);
2921 
2922             this.resultType = that.resultType;
2923         }
2924 
2925         @Override
2926         public SwitchExpressionOp transform(CopyContext cc, OpTransformer ot) {
2927             return new SwitchExpressionOp(this, cc, ot);
2928         }
2929 
2930         SwitchExpressionOp(TypeElement resultType, Value target, List<Body.Builder> bodyCs) {
2931             super(NAME, target, bodyCs);
2932 
2933             this.resultType = resultType == null ? bodies.get(1).yieldType() : resultType;
2934         }
2935 
2936         @Override
2937         public TypeElement resultType() {
2938             return resultType;
2939         }
2940     }
2941 
2942     /**
2943      * The switch statement operation, that can model Java language switch statement.
2944      */
2945     @OpDeclaration(SwitchStatementOp.NAME)
2946     public static final class SwitchStatementOp extends JavaSwitchOp
2947             implements JavaStatement {
2948         static final String NAME = "java.switch.statement";
2949 
2950         SwitchStatementOp(ExternalizedOp def) {
2951             this(def.operands().get(0), def.bodyDefinitions());
2952         }
2953 
2954         SwitchStatementOp(SwitchStatementOp that, CopyContext cc, OpTransformer ot) {
2955             super(that, cc, ot);
2956         }
2957 
2958         @Override
2959         public SwitchStatementOp transform(CopyContext cc, OpTransformer ot) {
2960             return new SwitchStatementOp(this, cc, ot);
2961         }
2962 
2963         SwitchStatementOp(Value target, List<Body.Builder> bodyCs) {
2964             super(NAME, target, bodyCs);
2965         }
2966 
2967         @Override
2968         public TypeElement resultType() {
2969             return VOID;
2970         }
2971     }
2972 
2973     /**
2974      * The switch fall-through operation, that can model fall-through to the next statement in the switch block after
2975      * the last statement of the current switch label.
2976      */
2977     @OpDeclaration(SwitchFallthroughOp.NAME)
2978     public static final class SwitchFallthroughOp extends JavaOp
2979             implements Op.BodyTerminating, Op.Lowerable {
2980         static final String NAME = "java.switch.fallthrough";
2981 
2982         SwitchFallthroughOp(ExternalizedOp def) {
2983             this();
2984         }
2985 
2986         SwitchFallthroughOp(SwitchFallthroughOp that, CopyContext cc) {
2987             super(that, cc);
2988         }
2989 
2990         @Override
2991         public SwitchFallthroughOp transform(CopyContext cc, OpTransformer ot) {
2992             return new SwitchFallthroughOp(this, cc);
2993         }
2994 
2995         SwitchFallthroughOp() {
2996             super(NAME, List.of());
2997         }
2998 
2999         @Override
3000         public TypeElement resultType() {
3001             return VOID;
3002         }
3003 
3004         @Override
3005         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
3006             return lower(b, BranchTarget::continueBlock);
3007         }
3008 
3009         Block.Builder lower(Block.Builder b, Function<BranchTarget, Block.Builder> f) {
3010             BranchTarget t = getBranchTarget(b.context(), ancestorBody());
3011             if (t != null) {
3012                 b.op(branch(f.apply(t).successor()));
3013             } else {
3014                 throw new IllegalStateException("No branch target for operation: " + this);
3015             }
3016             return b;
3017         }
3018     }
3019 
3020     /**
3021      * The for operation, that can model a Java language for statement.
3022      */
3023     @OpDeclaration(ForOp.NAME)
3024     public static final class ForOp extends JavaOp
3025             implements Op.Loop, Op.Lowerable, JavaStatement {
3026 
3027         public static final class InitBuilder {
3028             final Body.Builder ancestorBody;
3029             final List<? extends TypeElement> initTypes;
3030 
3031             InitBuilder(Body.Builder ancestorBody,
3032                         List<? extends TypeElement> initTypes) {
3033                 this.ancestorBody = ancestorBody;
3034                 this.initTypes = initTypes.stream().map(CoreType::varType).toList();
3035             }
3036 
3037             public ForOp.CondBuilder init(Consumer<Block.Builder> c) {
3038                 Body.Builder init = Body.Builder.of(ancestorBody,
3039                         CoreType.functionType(CoreType.tupleType(initTypes)));
3040                 c.accept(init.entryBlock());
3041 
3042                 return new CondBuilder(ancestorBody, initTypes, init);
3043             }
3044         }
3045 
3046         public static final class CondBuilder {
3047             final Body.Builder ancestorBody;
3048             final List<? extends TypeElement> initTypes;
3049             final Body.Builder init;
3050 
3051             public CondBuilder(Body.Builder ancestorBody,
3052                                List<? extends TypeElement> initTypes,
3053                                Body.Builder init) {
3054                 this.ancestorBody = ancestorBody;
3055                 this.initTypes = initTypes;
3056                 this.init = init;
3057             }
3058 
3059             public ForOp.UpdateBuilder cond(Consumer<Block.Builder> c) {
3060                 Body.Builder cond = Body.Builder.of(ancestorBody,
3061                         CoreType.functionType(BOOLEAN, initTypes));
3062                 c.accept(cond.entryBlock());
3063 
3064                 return new UpdateBuilder(ancestorBody, initTypes, init, cond);
3065             }
3066         }
3067 
3068         public static final class UpdateBuilder {
3069             final Body.Builder ancestorBody;
3070             final List<? extends TypeElement> initTypes;
3071             final Body.Builder init;
3072             final Body.Builder cond;
3073 
3074             public UpdateBuilder(Body.Builder ancestorBody,
3075                                  List<? extends TypeElement> initTypes,
3076                                  Body.Builder init, Body.Builder cond) {
3077                 this.ancestorBody = ancestorBody;
3078                 this.initTypes = initTypes;
3079                 this.init = init;
3080                 this.cond = cond;
3081             }
3082 
3083             public ForOp.BodyBuilder cond(Consumer<Block.Builder> c) {
3084                 Body.Builder update = Body.Builder.of(ancestorBody,
3085                         CoreType.functionType(VOID, initTypes));
3086                 c.accept(update.entryBlock());
3087 
3088                 return new BodyBuilder(ancestorBody, initTypes, init, cond, update);
3089             }
3090 
3091         }
3092 
3093         public static final class BodyBuilder {
3094             final Body.Builder ancestorBody;
3095             final List<? extends TypeElement> initTypes;
3096             final Body.Builder init;
3097             final Body.Builder cond;
3098             final Body.Builder update;
3099 
3100             public BodyBuilder(Body.Builder ancestorBody,
3101                                List<? extends TypeElement> initTypes,
3102                                Body.Builder init, Body.Builder cond, Body.Builder update) {
3103                 this.ancestorBody = ancestorBody;
3104                 this.initTypes = initTypes;
3105                 this.init = init;
3106                 this.cond = cond;
3107                 this.update = update;
3108             }
3109 
3110             public ForOp body(Consumer<Block.Builder> c) {
3111                 Body.Builder body = Body.Builder.of(ancestorBody,
3112                         CoreType.functionType(VOID, initTypes));
3113                 c.accept(body.entryBlock());
3114 
3115                 return new ForOp(init, cond, update, body);
3116             }
3117         }
3118 
3119         static final String NAME = "java.for";
3120 
3121         final Body init;
3122         final Body cond;
3123         final Body update;
3124         final Body body;
3125 
3126         static ForOp create(ExternalizedOp def) {
3127             return new ForOp(def);
3128         }
3129 
3130         ForOp(ExternalizedOp def) {
3131             this(def.bodyDefinitions().get(0),
3132                     def.bodyDefinitions().get(1),
3133                     def.bodyDefinitions().get(2),
3134                     def.bodyDefinitions().get(3));
3135         }
3136 
3137         ForOp(ForOp that, CopyContext cc, OpTransformer ot) {
3138             super(that, cc);
3139 
3140             this.init = that.init.transform(cc, ot).build(this);
3141             this.cond = that.cond.transform(cc, ot).build(this);
3142             this.update = that.update.transform(cc, ot).build(this);
3143             this.body = that.body.transform(cc, ot).build(this);
3144         }
3145 
3146         @Override
3147         public ForOp transform(CopyContext cc, OpTransformer ot) {
3148             return new ForOp(this, cc, ot);
3149         }
3150 
3151         ForOp(Body.Builder initC,
3152               Body.Builder condC,
3153               Body.Builder updateC,
3154               Body.Builder bodyC) {
3155             super(NAME, List.of());
3156 
3157             this.init = initC.build(this);
3158 
3159             this.cond = condC.build(this);
3160 
3161             this.update = updateC.build(this);
3162             if (!update.bodyType().returnType().equals(VOID)) {
3163                 throw new IllegalArgumentException("Update should return void: " + update.bodyType());
3164             }
3165 
3166             this.body = bodyC.build(this);
3167             if (!body.bodyType().returnType().equals(VOID)) {
3168                 throw new IllegalArgumentException("Body should return void: " + body.bodyType());
3169             }
3170         }
3171 
3172         @Override
3173         public List<Body> bodies() {
3174             return List.of(init, cond, update, body);
3175         }
3176 
3177         public Body init() {
3178             return init;
3179         }
3180 
3181         public Body cond() {
3182             return cond;
3183         }
3184 
3185         public Body update() {
3186             return update;
3187         }
3188 
3189         @Override
3190         public Body loopBody() {
3191             return body;
3192         }
3193 
3194         @Override
3195         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
3196             Block.Builder header = b.block();
3197             Block.Builder body = b.block();
3198             Block.Builder update = b.block();
3199             Block.Builder exit = b.block();
3200 
3201             List<Value> initValues = new ArrayList<>();
3202             // @@@ Init body has one yield operation yielding
3203             //  void, a single variable, or a tuple of one or more variables
3204             b.transformBody(init, List.of(), opT.andThen((block, op) -> {
3205                 if (op instanceof CoreOp.TupleOp) {
3206                     // Drop Tuple if a yielded
3207                     boolean isResult = op.result().uses().size() == 1 &&
3208                             op.result().uses().stream().allMatch(r -> r.op() instanceof CoreOp.YieldOp);
3209                     if (!isResult) {
3210                         block.op(op);
3211                     }
3212                 } else if (op instanceof CoreOp.YieldOp yop) {
3213                     if (yop.yieldValue() == null) {
3214                         block.op(branch(header.successor()));
3215                         return block;
3216                     } else if (yop.yieldValue() instanceof Result or) {
3217                         if (or.op() instanceof CoreOp.TupleOp top) {
3218                             initValues.addAll(block.context().getValues(top.operands()));
3219                         } else {
3220                             initValues.addAll(block.context().getValues(yop.operands()));
3221                         }
3222                         block.op(branch(header.successor()));
3223                         return block;
3224                     }
3225 
3226                     throw new IllegalStateException("Bad yield operation");
3227                 } else {
3228                     // @@@ Composition of lowerable ops
3229                     block.op(op);
3230                 }
3231                 return block;
3232             }));
3233 
3234             header.transformBody(cond, initValues, opT.andThen((block, op) -> {
3235                 if (op instanceof CoreOp.YieldOp yo) {
3236                     block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
3237                             body.successor(), exit.successor()));
3238                 } else if (op instanceof Lowerable lop) {
3239                     // @@@ Composition of lowerable ops
3240                     block = lop.lower(block, opT);
3241                 } else {
3242                     block.op(op);
3243                 }
3244                 return block;
3245             }));
3246 
3247             setBranchTarget(b.context(), this, new BranchTarget(exit, update));
3248 
3249             body.transformBody(this.body, initValues, opT.andThen((block, op) -> {
3250                 // @@@ Composition of lowerable ops
3251                 if (op instanceof Lowerable lop) {
3252                     block = lop.lower(block, opT);
3253                 } else {
3254                     block.op(op);
3255                 }
3256                 return block;
3257             }));
3258 
3259             update.transformBody(this.update, initValues, opT.andThen((block, op) -> {
3260                 if (op instanceof CoreOp.YieldOp) {
3261                     block.op(branch(header.successor()));
3262                 } else {
3263                     // @@@ Composition of lowerable ops
3264                     block.op(op);
3265                 }
3266                 return block;
3267             }));
3268 
3269             return exit;
3270         }
3271 
3272         @Override
3273         public TypeElement resultType() {
3274             return VOID;
3275         }
3276     }
3277 
3278     /**
3279      * The enhanced for operation, that can model a Java language enhanced for statement.
3280      */
3281     @OpDeclaration(EnhancedForOp.NAME)
3282     public static final class EnhancedForOp extends JavaOp
3283             implements Op.Loop, Op.Lowerable, JavaStatement {
3284 
3285         public static final class ExpressionBuilder {
3286             final Body.Builder ancestorBody;
3287             final TypeElement iterableType;
3288             final TypeElement elementType;
3289 
3290             ExpressionBuilder(Body.Builder ancestorBody,
3291                               TypeElement iterableType, TypeElement elementType) {
3292                 this.ancestorBody = ancestorBody;
3293                 this.iterableType = iterableType;
3294                 this.elementType = elementType;
3295             }
3296 
3297             public DefinitionBuilder expression(Consumer<Block.Builder> c) {
3298                 Body.Builder expression = Body.Builder.of(ancestorBody,
3299                         CoreType.functionType(iterableType));
3300                 c.accept(expression.entryBlock());
3301 
3302                 return new DefinitionBuilder(ancestorBody, elementType, expression);
3303             }
3304         }
3305 
3306         public static final class DefinitionBuilder {
3307             final Body.Builder ancestorBody;
3308             final TypeElement elementType;
3309             final Body.Builder expression;
3310 
3311             DefinitionBuilder(Body.Builder ancestorBody,
3312                               TypeElement elementType, Body.Builder expression) {
3313                 this.ancestorBody = ancestorBody;
3314                 this.elementType = elementType;
3315                 this.expression = expression;
3316             }
3317 
3318             public BodyBuilder definition(Consumer<Block.Builder> c) {
3319                 return definition(elementType, c);
3320             }
3321 
3322             public BodyBuilder definition(TypeElement bodyElementType, Consumer<Block.Builder> c) {
3323                 Body.Builder definition = Body.Builder.of(ancestorBody,
3324                         CoreType.functionType(bodyElementType, elementType));
3325                 c.accept(definition.entryBlock());
3326 
3327                 return new BodyBuilder(ancestorBody, elementType, expression, definition);
3328             }
3329         }
3330 
3331         public static final class BodyBuilder {
3332             final Body.Builder ancestorBody;
3333             final TypeElement elementType;
3334             final Body.Builder expression;
3335             final Body.Builder definition;
3336 
3337             BodyBuilder(Body.Builder ancestorBody,
3338                         TypeElement elementType, Body.Builder expression, Body.Builder definition) {
3339                 this.ancestorBody = ancestorBody;
3340                 this.elementType = elementType;
3341                 this.expression = expression;
3342                 this.definition = definition;
3343             }
3344 
3345             public EnhancedForOp body(Consumer<Block.Builder> c) {
3346                 Body.Builder body = Body.Builder.of(ancestorBody,
3347                         CoreType.functionType(VOID, elementType));
3348                 c.accept(body.entryBlock());
3349 
3350                 return new EnhancedForOp(expression, definition, body);
3351             }
3352         }
3353 
3354         static final String NAME = "java.enhancedFor";
3355 
3356         final Body expression;
3357         final Body init;
3358         final Body body;
3359 
3360         static EnhancedForOp create(ExternalizedOp def) {
3361             return new EnhancedForOp(def);
3362         }
3363 
3364         EnhancedForOp(ExternalizedOp def) {
3365             this(def.bodyDefinitions().get(0),
3366                     def.bodyDefinitions().get(1),
3367                     def.bodyDefinitions().get(2));
3368         }
3369 
3370         EnhancedForOp(EnhancedForOp that, CopyContext cc, OpTransformer ot) {
3371             super(that, cc);
3372 
3373             this.expression = that.expression.transform(cc, ot).build(this);
3374             this.init = that.init.transform(cc, ot).build(this);
3375             this.body = that.body.transform(cc, ot).build(this);
3376         }
3377 
3378         @Override
3379         public EnhancedForOp transform(CopyContext cc, OpTransformer ot) {
3380             return new EnhancedForOp(this, cc, ot);
3381         }
3382 
3383         EnhancedForOp(Body.Builder expressionC, Body.Builder initC, Body.Builder bodyC) {
3384             super(NAME, List.of());
3385 
3386             this.expression = expressionC.build(this);
3387             if (expression.bodyType().returnType().equals(VOID)) {
3388                 throw new IllegalArgumentException("Expression should return non-void value: " + expression.bodyType());
3389             }
3390             if (!expression.bodyType().parameterTypes().isEmpty()) {
3391                 throw new IllegalArgumentException("Expression should have zero parameters: " + expression.bodyType());
3392             }
3393 
3394             this.init = initC.build(this);
3395             if (init.bodyType().returnType().equals(VOID)) {
3396                 throw new IllegalArgumentException("Initialization should return non-void value: " + init.bodyType());
3397             }
3398             if (init.bodyType().parameterTypes().size() != 1) {
3399                 throw new IllegalArgumentException("Initialization should have one parameter: " + init.bodyType());
3400             }
3401 
3402             this.body = bodyC.build(this);
3403             if (!body.bodyType().returnType().equals(VOID)) {
3404                 throw new IllegalArgumentException("Body should return void: " + body.bodyType());
3405             }
3406             if (body.bodyType().parameterTypes().size() != 1) {
3407                 throw new IllegalArgumentException("Body should have one parameter: " + body.bodyType());
3408             }
3409         }
3410 
3411         @Override
3412         public List<Body> bodies() {
3413             return List.of(expression, init, body);
3414         }
3415 
3416         public Body expression() {
3417             return expression;
3418         }
3419 
3420         public Body initialization() {
3421             return init;
3422         }
3423 
3424         @Override
3425         public Body loopBody() {
3426             return body;
3427         }
3428 
3429         static final MethodRef ITERABLE_ITERATOR = MethodRef.method(Iterable.class, "iterator", Iterator.class);
3430         static final MethodRef ITERATOR_HAS_NEXT = MethodRef.method(Iterator.class, "hasNext", boolean.class);
3431         static final MethodRef ITERATOR_NEXT = MethodRef.method(Iterator.class, "next", Object.class);
3432 
3433         @Override
3434         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
3435             JavaType elementType = (JavaType) init.entryBlock().parameters().get(0).type();
3436             boolean isArray = expression.bodyType().returnType() instanceof ArrayType;
3437 
3438             Block.Builder preHeader = b.block(expression.bodyType().returnType());
3439             Block.Builder header = b.block(isArray ? List.of(INT) : List.of());
3440             Block.Builder init = b.block();
3441             Block.Builder body = b.block();
3442             Block.Builder exit = b.block();
3443 
3444             b.transformBody(expression, List.of(), opT.andThen((block, op) -> {
3445                 if (op instanceof CoreOp.YieldOp yop) {
3446                     Value loopSource = block.context().getValue(yop.yieldValue());
3447                     block.op(branch(preHeader.successor(loopSource)));
3448                 } else {
3449                     // @@@ Composition of lowerable ops
3450                     block.op(op);
3451                 }
3452                 return block;
3453             }));
3454 
3455             if (isArray) {
3456                 Value array = preHeader.parameters().get(0);
3457                 Value arrayLength = preHeader.op(arrayLength(array));
3458                 Value i = preHeader.op(constant(INT, 0));
3459                 preHeader.op(branch(header.successor(i)));
3460 
3461                 i = header.parameters().get(0);
3462                 Value p = header.op(lt(i, arrayLength));
3463                 header.op(conditionalBranch(p, init.successor(), exit.successor()));
3464 
3465                 Value e = init.op(arrayLoadOp(array, i));
3466                 List<Value> initValues = new ArrayList<>();
3467                 // @@@ Init body has one yield operation yielding a single variable
3468                 init.transformBody(this.init, List.of(e), (block, op) -> {
3469                     if (op instanceof CoreOp.YieldOp yop) {
3470                         initValues.addAll(block.context().getValues(yop.operands()));
3471                         block.op(branch(body.successor()));
3472                     } else {
3473                         // @@@ Composition of lowerable ops
3474                         block.op(op);
3475                     }
3476                     return block;
3477                 });
3478 
3479                 Block.Builder update = b.block();
3480                 setBranchTarget(b.context(), this, new BranchTarget(exit, update));
3481 
3482                 body.transformBody(this.body, initValues, opT.andThen((block, op) -> {
3483                     // @@@ Composition of lowerable ops
3484                     if (op instanceof Lowerable lop) {
3485                         block = lop.lower(block, opT);
3486                     } else {
3487                         block.op(op);
3488                     }
3489                     return block;
3490                 }));
3491 
3492                 i = update.op(add(i, update.op(constant(INT, 1))));
3493                 update.op(branch(header.successor(i)));
3494             } else {
3495                 JavaType iterable = parameterized(type(Iterator.class), elementType);
3496                 Value iterator = preHeader.op(invoke(iterable, ITERABLE_ITERATOR, preHeader.parameters().get(0)));
3497                 preHeader.op(branch(header.successor()));
3498 
3499                 Value p = header.op(invoke(ITERATOR_HAS_NEXT, iterator));
3500                 header.op(conditionalBranch(p, init.successor(), exit.successor()));
3501 
3502                 Value e = init.op(invoke(elementType, ITERATOR_NEXT, iterator));
3503                 List<Value> initValues = new ArrayList<>();
3504                 init.transformBody(this.init, List.of(e), opT.andThen((block, op) -> {
3505                     if (op instanceof CoreOp.YieldOp yop) {
3506                         initValues.addAll(block.context().getValues(yop.operands()));
3507                         block.op(branch(body.successor()));
3508                     } else {
3509                         // @@@ Composition of lowerable ops
3510                         block.op(op);
3511                     }
3512                     return block;
3513                 }));
3514 
3515                 setBranchTarget(b.context(), this, new BranchTarget(exit, header));
3516 
3517                 body.transformBody(this.body, initValues, opT.andThen((block, op) -> {
3518                     // @@@ Composition of lowerable ops
3519                     if (op instanceof Lowerable lop) {
3520                         block = lop.lower(block, opT);
3521                     } else {
3522                         block.op(op);
3523                     }
3524                     return block;
3525                 }));
3526             }
3527 
3528             return exit;
3529         }
3530 
3531         @Override
3532         public TypeElement resultType() {
3533             return VOID;
3534         }
3535     }
3536 
3537     /**
3538      * The while operation, that can model a Java language while statement.
3539      */
3540     @OpDeclaration(WhileOp.NAME)
3541     public static final class WhileOp extends JavaOp
3542             implements Op.Loop, Op.Lowerable, JavaStatement {
3543 
3544         public static class PredicateBuilder {
3545             final Body.Builder ancestorBody;
3546 
3547             PredicateBuilder(Body.Builder ancestorBody) {
3548                 this.ancestorBody = ancestorBody;
3549             }
3550 
3551             public WhileOp.BodyBuilder predicate(Consumer<Block.Builder> c) {
3552                 Body.Builder body = Body.Builder.of(ancestorBody, CoreType.functionType(BOOLEAN));
3553                 c.accept(body.entryBlock());
3554 
3555                 return new WhileOp.BodyBuilder(ancestorBody, body);
3556             }
3557         }
3558 
3559         public static class BodyBuilder {
3560             final Body.Builder ancestorBody;
3561             private final Body.Builder predicate;
3562 
3563             BodyBuilder(Body.Builder ancestorBody, Body.Builder predicate) {
3564                 this.ancestorBody = ancestorBody;
3565                 this.predicate = predicate;
3566             }
3567 
3568             public WhileOp body(Consumer<Block.Builder> c) {
3569                 Body.Builder body = Body.Builder.of(ancestorBody, CoreType.FUNCTION_TYPE_VOID);
3570                 c.accept(body.entryBlock());
3571 
3572                 return new WhileOp(List.of(predicate, body));
3573             }
3574         }
3575 
3576         private static final String NAME = "java.while";
3577 
3578         private final List<Body> bodies;
3579 
3580         WhileOp(ExternalizedOp def) {
3581             this(def.bodyDefinitions());
3582         }
3583 
3584         WhileOp(List<Body.Builder> bodyCs) {
3585             super(NAME, List.of());
3586 
3587             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
3588         }
3589 
3590         WhileOp(Body.Builder predicate, Body.Builder body) {
3591             super(NAME, List.of());
3592 
3593             Objects.requireNonNull(body);
3594 
3595             this.bodies = Stream.of(predicate, body).filter(Objects::nonNull)
3596                     .map(bc -> bc.build(this)).toList();
3597 
3598             // @@@ This will change with pattern bindings
3599             if (!bodies.get(0).bodyType().equals(CoreType.functionType(BOOLEAN))) {
3600                 throw new IllegalArgumentException(
3601                         "Predicate body descriptor should be " + CoreType.functionType(BOOLEAN) +
3602                                 " but is " + bodies.get(0).bodyType());
3603             }
3604             if (!bodies.get(1).bodyType().equals(CoreType.FUNCTION_TYPE_VOID)) {
3605                 throw new IllegalArgumentException(
3606                         "Body descriptor should be " + CoreType.functionType(VOID) +
3607                                 " but is " + bodies.get(1).bodyType());
3608             }
3609         }
3610 
3611         WhileOp(WhileOp that, CopyContext cc, OpTransformer ot) {
3612             super(that, cc);
3613 
3614             this.bodies = that.bodies.stream()
3615                     .map(b -> b.transform(cc, ot).build(this)).toList();
3616         }
3617 
3618         @Override
3619         public WhileOp transform(CopyContext cc, OpTransformer ot) {
3620             return new WhileOp(this, cc, ot);
3621         }
3622 
3623         @Override
3624         public List<Body> bodies() {
3625             return bodies;
3626         }
3627 
3628         public Body predicateBody() {
3629             return bodies.get(0);
3630         }
3631 
3632         @Override
3633         public Body loopBody() {
3634             return bodies.get(1);
3635         }
3636 
3637         @Override
3638         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
3639             Block.Builder header = b.block();
3640             Block.Builder body = b.block();
3641             Block.Builder exit = b.block();
3642 
3643             b.op(branch(header.successor()));
3644 
3645             header.transformBody(predicateBody(), List.of(), opT.andThen((block, op) -> {
3646                 if (op instanceof CoreOp.YieldOp yo) {
3647                     block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
3648                             body.successor(), exit.successor()));
3649                 } else if (op instanceof Lowerable lop) {
3650                     // @@@ Composition of lowerable ops
3651                     block = lop.lower(block, opT);
3652                 } else {
3653                     block.op(op);
3654                 }
3655                 return block;
3656             }));
3657 
3658             setBranchTarget(b.context(), this, new BranchTarget(exit, header));
3659 
3660             body.transformBody(loopBody(), List.of(), opT.andThen((block, op) -> {
3661                 // @@@ Composition of lowerable ops
3662                 if (op instanceof Lowerable lop) {
3663                     block = lop.lower(block, opT);
3664                 } else {
3665                     block.op(op);
3666                 }
3667                 return block;
3668             }));
3669 
3670             return exit;
3671         }
3672 
3673         @Override
3674         public TypeElement resultType() {
3675             return VOID;
3676         }
3677     }
3678 
3679     /**
3680      * The do-while operation, that can model a Java language do statement.
3681      */
3682     // @@@ Unify JavaDoWhileOp and JavaWhileOp with common abstract superclass
3683     @OpDeclaration(DoWhileOp.NAME)
3684     public static final class DoWhileOp extends JavaOp
3685             implements Op.Loop, Op.Lowerable, JavaStatement {
3686 
3687         public static class PredicateBuilder {
3688             final Body.Builder ancestorBody;
3689             private final Body.Builder body;
3690 
3691             PredicateBuilder(Body.Builder ancestorBody, Body.Builder body) {
3692                 this.ancestorBody = ancestorBody;
3693                 this.body = body;
3694             }
3695 
3696             public DoWhileOp predicate(Consumer<Block.Builder> c) {
3697                 Body.Builder predicate = Body.Builder.of(ancestorBody, CoreType.functionType(BOOLEAN));
3698                 c.accept(predicate.entryBlock());
3699 
3700                 return new DoWhileOp(List.of(body, predicate));
3701             }
3702         }
3703 
3704         public static class BodyBuilder {
3705             final Body.Builder ancestorBody;
3706 
3707             BodyBuilder(Body.Builder ancestorBody) {
3708                 this.ancestorBody = ancestorBody;
3709             }
3710 
3711             public DoWhileOp.PredicateBuilder body(Consumer<Block.Builder> c) {
3712                 Body.Builder body = Body.Builder.of(ancestorBody, CoreType.FUNCTION_TYPE_VOID);
3713                 c.accept(body.entryBlock());
3714 
3715                 return new DoWhileOp.PredicateBuilder(ancestorBody, body);
3716             }
3717         }
3718 
3719         private static final String NAME = "java.do.while";
3720 
3721         private final List<Body> bodies;
3722 
3723         DoWhileOp(ExternalizedOp def) {
3724             this(def.bodyDefinitions());
3725         }
3726 
3727         DoWhileOp(List<Body.Builder> bodyCs) {
3728             super(NAME, List.of());
3729 
3730             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
3731         }
3732 
3733         DoWhileOp(Body.Builder body, Body.Builder predicate) {
3734             super(NAME, List.of());
3735 
3736             Objects.requireNonNull(body);
3737 
3738             this.bodies = Stream.of(body, predicate).filter(Objects::nonNull)
3739                     .map(bc -> bc.build(this)).toList();
3740 
3741             if (!bodies.get(0).bodyType().equals(CoreType.FUNCTION_TYPE_VOID)) {
3742                 throw new IllegalArgumentException(
3743                         "Body descriptor should be " + CoreType.functionType(VOID) +
3744                                 " but is " + bodies.get(1).bodyType());
3745             }
3746             if (!bodies.get(1).bodyType().equals(CoreType.functionType(BOOLEAN))) {
3747                 throw new IllegalArgumentException(
3748                         "Predicate body descriptor should be " + CoreType.functionType(BOOLEAN) +
3749                                 " but is " + bodies.get(0).bodyType());
3750             }
3751         }
3752 
3753         DoWhileOp(DoWhileOp that, CopyContext cc, OpTransformer ot) {
3754             super(that, cc);
3755 
3756             this.bodies = that.bodies.stream()
3757                     .map(b -> b.transform(cc, ot).build(this)).toList();
3758         }
3759 
3760         @Override
3761         public DoWhileOp transform(CopyContext cc, OpTransformer ot) {
3762             return new DoWhileOp(this, cc, ot);
3763         }
3764 
3765         @Override
3766         public List<Body> bodies() {
3767             return bodies;
3768         }
3769 
3770         public Body predicateBody() {
3771             return bodies.get(1);
3772         }
3773 
3774         @Override
3775         public Body loopBody() {
3776             return bodies.get(0);
3777         }
3778 
3779         @Override
3780         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
3781             Block.Builder body = b.block();
3782             Block.Builder header = b.block();
3783             Block.Builder exit = b.block();
3784 
3785             b.op(branch(body.successor()));
3786 
3787             setBranchTarget(b.context(), this, new BranchTarget(exit, header));
3788 
3789             body.transformBody(loopBody(), List.of(), opT.andThen((block, op) -> {
3790                 // @@@ Composition of lowerable ops
3791                 if (op instanceof Lowerable lop) {
3792                     block = lop.lower(block, opT);
3793                 } else {
3794                     block.op(op);
3795                 }
3796                 return block;
3797             }));
3798 
3799             header.transformBody(predicateBody(), List.of(), opT.andThen((block, op) -> {
3800                 if (op instanceof CoreOp.YieldOp yo) {
3801                     block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
3802                             body.successor(), exit.successor()));
3803                 } else if (op instanceof Lowerable lop) {
3804                     // @@@ Composition of lowerable ops
3805                     block = lop.lower(block, opT);
3806                 } else {
3807                     block.op(op);
3808                 }
3809                 return block;
3810             }));
3811 
3812             return exit;
3813         }
3814 
3815         @Override
3816         public TypeElement resultType() {
3817             return VOID;
3818         }
3819     }
3820 
3821     /**
3822      * The conditional-and-or operation, that can model Java language condition-or or conditional-and expressions.
3823      */
3824     public sealed static abstract class JavaConditionalOp extends JavaOp
3825             implements Op.Nested, Op.Lowerable, JavaExpression {
3826         final List<Body> bodies;
3827 
3828         JavaConditionalOp(JavaConditionalOp that, CopyContext cc, OpTransformer ot) {
3829             super(that, cc);
3830 
3831             // Copy body
3832             this.bodies = that.bodies.stream().map(b -> b.transform(cc, ot).build(this)).toList();
3833         }
3834 
3835         JavaConditionalOp(String name, List<Body.Builder> bodyCs) {
3836             super(name, List.of());
3837 
3838             if (bodyCs.isEmpty()) {
3839                 throw new IllegalArgumentException();
3840             }
3841 
3842             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
3843             for (Body b : bodies) {
3844                 if (!b.bodyType().equals(CoreType.functionType(BOOLEAN))) {
3845                     throw new IllegalArgumentException("Body conditional body descriptor: " + b.bodyType());
3846                 }
3847             }
3848         }
3849 
3850         @Override
3851         public List<Body> bodies() {
3852             return bodies;
3853         }
3854 
3855         static Block.Builder lower(Block.Builder startBlock, OpTransformer opT, JavaConditionalOp cop) {
3856             List<Body> bodies = cop.bodies();
3857 
3858             Block.Builder exit = startBlock.block();
3859             TypeElement oprType = cop.result().type();
3860             Block.Parameter arg = exit.parameter(oprType);
3861             startBlock.context().mapValue(cop.result(), arg);
3862 
3863             // Transform bodies in reverse order
3864             // This makes available the blocks to be referenced as successors in prior blocks
3865 
3866             Block.Builder pred = null;
3867             for (int i = bodies.size() - 1; i >= 0; i--) {
3868                 OpTransformer opt;
3869                 if (i == bodies.size() - 1) {
3870                     opt = (block, op) -> {
3871                         if (op instanceof CoreOp.YieldOp yop) {
3872                             Value p = block.context().getValue(yop.yieldValue());
3873                             block.op(branch(exit.successor(p)));
3874                         } else if (op instanceof Lowerable lop) {
3875                             // @@@ Composition of lowerable ops
3876                             block = lop.lower(block, opT);
3877                         } else {
3878                             // Copy
3879                             block.op(op);
3880                         }
3881                         return block;
3882                     };
3883                 } else {
3884                     Block.Builder nextPred = pred;
3885                     opt = (block, op) -> {
3886                         if (op instanceof CoreOp.YieldOp yop) {
3887                             Value p = block.context().getValue(yop.yieldValue());
3888                             if (cop instanceof ConditionalAndOp) {
3889                                 block.op(conditionalBranch(p, nextPred.successor(), exit.successor(p)));
3890                             } else {
3891                                 block.op(conditionalBranch(p, exit.successor(p), nextPred.successor()));
3892                             }
3893                         } else if (op instanceof Lowerable lop) {
3894                             // @@@ Composition of lowerable ops
3895                             block = lop.lower(block, opT);
3896                         } else {
3897                             // Copy
3898                             block.op(op);
3899                         }
3900                         return block;
3901                     };
3902                 }
3903 
3904                 Body fromPred = bodies.get(i);
3905                 if (i == 0) {
3906                     startBlock.transformBody(fromPred, List.of(), opt);
3907                 } else {
3908                     pred = startBlock.block(fromPred.bodyType().parameterTypes());
3909                     pred.transformBody(fromPred, pred.parameters(), opT.andThen(opt));
3910                 }
3911             }
3912 
3913             return exit;
3914         }
3915 
3916         @Override
3917         public TypeElement resultType() {
3918             return BOOLEAN;
3919         }
3920     }
3921 
3922     /**
3923      * The conditional-and operation, that can model Java language conditional-and expressions.
3924      */
3925     @OpDeclaration(ConditionalAndOp.NAME)
3926     public static final class ConditionalAndOp extends JavaConditionalOp {
3927 
3928         public static class Builder {
3929             final Body.Builder ancestorBody;
3930             final List<Body.Builder> bodies;
3931 
3932             Builder(Body.Builder ancestorBody, Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
3933                 this.ancestorBody = ancestorBody;
3934                 this.bodies = new ArrayList<>();
3935                 and(lhs);
3936                 and(rhs);
3937             }
3938 
3939             public Builder and(Consumer<Block.Builder> c) {
3940                 Body.Builder body = Body.Builder.of(ancestorBody, CoreType.functionType(BOOLEAN));
3941                 c.accept(body.entryBlock());
3942                 bodies.add(body);
3943 
3944                 return this;
3945             }
3946 
3947             public ConditionalAndOp build() {
3948                 return new ConditionalAndOp(bodies);
3949             }
3950         }
3951 
3952         static final String NAME = "java.cand";
3953 
3954         ConditionalAndOp(ExternalizedOp def) {
3955             this(def.bodyDefinitions());
3956         }
3957 
3958         ConditionalAndOp(ConditionalAndOp that, CopyContext cc, OpTransformer ot) {
3959             super(that, cc, ot);
3960         }
3961 
3962         @Override
3963         public ConditionalAndOp transform(CopyContext cc, OpTransformer ot) {
3964             return new ConditionalAndOp(this, cc, ot);
3965         }
3966 
3967         ConditionalAndOp(List<Body.Builder> bodyCs) {
3968             super(NAME, bodyCs);
3969         }
3970 
3971         @Override
3972         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
3973             return lower(b, opT, this);
3974         }
3975     }
3976 
3977     /**
3978      * The conditional-or operation, that can model Java language conditional-or expressions.
3979      */
3980     @OpDeclaration(ConditionalOrOp.NAME)
3981     public static final class ConditionalOrOp extends JavaConditionalOp {
3982 
3983         public static class Builder {
3984             final Body.Builder ancestorBody;
3985             final List<Body.Builder> bodies;
3986 
3987             Builder(Body.Builder ancestorBody, Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
3988                 this.ancestorBody = ancestorBody;
3989                 this.bodies = new ArrayList<>();
3990                 or(lhs);
3991                 or(rhs);
3992             }
3993 
3994             public Builder or(Consumer<Block.Builder> c) {
3995                 Body.Builder body = Body.Builder.of(ancestorBody, CoreType.functionType(BOOLEAN));
3996                 c.accept(body.entryBlock());
3997                 bodies.add(body);
3998 
3999                 return this;
4000             }
4001 
4002             public ConditionalOrOp build() {
4003                 return new ConditionalOrOp(bodies);
4004             }
4005         }
4006 
4007         static final String NAME = "java.cor";
4008 
4009         ConditionalOrOp(ExternalizedOp def) {
4010             this(def.bodyDefinitions());
4011         }
4012 
4013         ConditionalOrOp(ConditionalOrOp that, CopyContext cc, OpTransformer ot) {
4014             super(that, cc, ot);
4015         }
4016 
4017         @Override
4018         public ConditionalOrOp transform(CopyContext cc, OpTransformer ot) {
4019             return new ConditionalOrOp(this, cc, ot);
4020         }
4021 
4022         ConditionalOrOp(List<Body.Builder> bodyCs) {
4023             super(NAME, bodyCs);
4024         }
4025 
4026         @Override
4027         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
4028             return lower(b, opT, this);
4029         }
4030     }
4031 
4032     /**
4033      * The conditional operation, that can model Java language conditional operator {@code ?} expressions.
4034      */
4035     @OpDeclaration(ConditionalExpressionOp.NAME)
4036     public static final class ConditionalExpressionOp extends JavaOp
4037             implements Op.Nested, Op.Lowerable, JavaExpression {
4038 
4039         static final String NAME = "java.cexpression";
4040 
4041         final TypeElement resultType;
4042         // {cond, truepart, falsepart}
4043         final List<Body> bodies;
4044 
4045         ConditionalExpressionOp(ExternalizedOp def) {
4046             if (!def.operands().isEmpty()) {
4047                 throw new IllegalStateException("Operation must have no operands");
4048             }
4049 
4050             this(def.resultType(), def.bodyDefinitions());
4051         }
4052 
4053         ConditionalExpressionOp(ConditionalExpressionOp that, CopyContext cc, OpTransformer ot) {
4054             super(that, cc);
4055 
4056             // Copy body
4057             this.bodies = that.bodies.stream()
4058                     .map(b -> b.transform(cc, ot).build(this)).toList();
4059             this.resultType = that.resultType;
4060         }
4061 
4062         @Override
4063         public ConditionalExpressionOp transform(CopyContext cc, OpTransformer ot) {
4064             return new ConditionalExpressionOp(this, cc, ot);
4065         }
4066 
4067         ConditionalExpressionOp(TypeElement expressionType, List<Body.Builder> bodyCs) {
4068             super(NAME, List.of());
4069 
4070             this.bodies = bodyCs.stream().map(bc -> bc.build(this)).toList();
4071             // @@@ when expressionType is null, we assume truepart and falsepart have the same yieldType
4072             this.resultType = expressionType == null ? bodies.get(1).yieldType() : expressionType;
4073 
4074             if (bodies.size() < 3) {
4075                 throw new IllegalArgumentException("Incorrect number of bodies: " + bodies.size());
4076             }
4077 
4078             Body cond = bodies.get(0);
4079             if (!cond.bodyType().equals(CoreType.functionType(BOOLEAN))) {
4080                 throw new IllegalArgumentException("Illegal cond body descriptor: " + cond.bodyType());
4081             }
4082         }
4083 
4084         @Override
4085         public List<Body> bodies() {
4086             return bodies;
4087         }
4088 
4089         @Override
4090         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
4091             Block.Builder exit = b.block(resultType());
4092             exit.context().mapValue(result(), exit.parameters().get(0));
4093 
4094             setBranchTarget(b.context(), this, new BranchTarget(exit, null));
4095 
4096             List<Block.Builder> builders = List.of(b.block(), b.block());
4097             b.transformBody(bodies.get(0), List.of(), opT.andThen((block, op) -> {
4098                 if (op instanceof CoreOp.YieldOp yo) {
4099                     block.op(conditionalBranch(block.context().getValue(yo.yieldValue()),
4100                             builders.get(0).successor(), builders.get(1).successor()));
4101                 } else if (op instanceof Lowerable lop) {
4102                     // @@@ Composition of lowerable ops
4103                     block = lop.lower(block, opT);
4104                 } else {
4105                     block.op(op);
4106                 }
4107                 return block;
4108             }));
4109 
4110             for (int i = 0; i < 2; i++) {
4111                 builders.get(i).transformBody(bodies.get(i + 1), List.of(), opT.andThen((block, op) -> {
4112                     if (op instanceof CoreOp.YieldOp yop) {
4113                         block.op(branch(exit.successor(block.context().getValue(yop.yieldValue()))));
4114                     } else if (op instanceof Lowerable lop) {
4115                         // @@@ Composition of lowerable ops
4116                         block = lop.lower(block, opT);
4117                     } else {
4118                         block.op(op);
4119                     }
4120                     return block;
4121                 }));
4122             }
4123 
4124             return exit;
4125         }
4126 
4127         @Override
4128         public TypeElement resultType() {
4129             return resultType;
4130         }
4131     }
4132 
4133     /**
4134      * The try operation, that can model Java language try statements.
4135      */
4136     @OpDeclaration(TryOp.NAME)
4137     public static final class TryOp extends JavaOp
4138             implements Op.Nested, Op.Lowerable, JavaStatement {
4139 
4140         public static final class BodyBuilder {
4141             final Body.Builder ancestorBody;
4142             final List<? extends TypeElement> resourceTypes;
4143             final Body.Builder resources;
4144 
4145             BodyBuilder(Body.Builder ancestorBody, List<? extends TypeElement> resourceTypes, Body.Builder resources) {
4146                 this.ancestorBody = ancestorBody;
4147                 this.resourceTypes = resourceTypes;
4148                 this.resources = resources;
4149             }
4150 
4151             public CatchBuilder body(Consumer<Block.Builder> c) {
4152                 Body.Builder body = Body.Builder.of(ancestorBody,
4153                         CoreType.functionType(VOID, resourceTypes));
4154                 c.accept(body.entryBlock());
4155 
4156                 return new CatchBuilder(ancestorBody, resources, body);
4157             }
4158         }
4159 
4160         public static final class CatchBuilder {
4161             final Body.Builder ancestorBody;
4162             final Body.Builder resources;
4163             final Body.Builder body;
4164             final List<Body.Builder> catchers;
4165 
4166             CatchBuilder(Body.Builder ancestorBody, Body.Builder resources, Body.Builder body) {
4167                 this.ancestorBody = ancestorBody;
4168                 this.resources = resources;
4169                 this.body = body;
4170                 this.catchers = new ArrayList<>();
4171             }
4172 
4173             // @@@ multi-catch
4174             public CatchBuilder catch_(TypeElement exceptionType, Consumer<Block.Builder> c) {
4175                 Body.Builder _catch = Body.Builder.of(ancestorBody,
4176                         CoreType.functionType(VOID, exceptionType));
4177                 c.accept(_catch.entryBlock());
4178                 catchers.add(_catch);
4179 
4180                 return this;
4181             }
4182 
4183             public TryOp finally_(Consumer<Block.Builder> c) {
4184                 Body.Builder _finally = Body.Builder.of(ancestorBody, CoreType.FUNCTION_TYPE_VOID);
4185                 c.accept(_finally.entryBlock());
4186 
4187                 return new TryOp(resources, body, catchers, _finally);
4188             }
4189 
4190             public TryOp noFinalizer() {
4191                 return new TryOp(resources, body, catchers, null);
4192             }
4193         }
4194 
4195         static final String NAME = "java.try";
4196 
4197         final Body resources;
4198         final Body body;
4199         final List<Body> catchers;
4200         final Body finalizer;
4201 
4202         static TryOp create(ExternalizedOp def) {
4203             return new TryOp(def);
4204         }
4205 
4206         TryOp(ExternalizedOp def) {
4207             List<Body.Builder> bodies = def.bodyDefinitions();
4208             Body.Builder first = bodies.getFirst();
4209             Body.Builder resources;
4210             Body.Builder body;
4211             if (first.bodyType().returnType().equals(VOID)) {
4212                 resources = null;
4213                 body = first;
4214             } else {
4215                 resources = first;
4216                 body = bodies.get(1);
4217             }
4218 
4219             Body.Builder last = bodies.getLast();
4220             Body.Builder finalizer;
4221             if (last.bodyType().parameterTypes().isEmpty()) {
4222                 finalizer = last;
4223             } else {
4224                 finalizer = null;
4225             }
4226             List<Body.Builder> catchers = bodies.subList(
4227                     resources == null ? 1 : 2,
4228                     bodies.size() - (finalizer == null ? 0 : 1));
4229 
4230             this(resources, body, catchers, finalizer);
4231         }
4232 
4233         TryOp(TryOp that, CopyContext cc, OpTransformer ot) {
4234             super(that, cc);
4235 
4236             if (that.resources != null) {
4237                 this.resources = that.resources.transform(cc, ot).build(this);
4238             } else {
4239                 this.resources = null;
4240             }
4241             this.body = that.body.transform(cc, ot).build(this);
4242             this.catchers = that.catchers.stream()
4243                     .map(b -> b.transform(cc, ot).build(this))
4244                     .toList();
4245             if (that.finalizer != null) {
4246                 this.finalizer = that.finalizer.transform(cc, ot).build(this);
4247             } else {
4248                 this.finalizer = null;
4249             }
4250         }
4251 
4252         @Override
4253         public TryOp transform(CopyContext cc, OpTransformer ot) {
4254             return new TryOp(this, cc, ot);
4255         }
4256 
4257         TryOp(Body.Builder resourcesC,
4258               Body.Builder bodyC,
4259               List<Body.Builder> catchersC,
4260               Body.Builder finalizerC) {
4261             super(NAME, List.of());
4262 
4263             if (resourcesC != null) {
4264                 this.resources = resourcesC.build(this);
4265                 if (resources.bodyType().returnType().equals(VOID)) {
4266                     throw new IllegalArgumentException("Resources should not return void: " + resources.bodyType());
4267                 }
4268                 if (!resources.bodyType().parameterTypes().isEmpty()) {
4269                     throw new IllegalArgumentException("Resources should have zero parameters: " + resources.bodyType());
4270                 }
4271             } else {
4272                 this.resources = null;
4273             }
4274 
4275             this.body = bodyC.build(this);
4276             if (!body.bodyType().returnType().equals(VOID)) {
4277                 throw new IllegalArgumentException("Try should return void: " + body.bodyType());
4278             }
4279 
4280             this.catchers = catchersC.stream().map(c -> c.build(this)).toList();
4281             for (Body _catch : catchers) {
4282                 if (!_catch.bodyType().returnType().equals(VOID)) {
4283                     throw new IllegalArgumentException("Catch should return void: " + _catch.bodyType());
4284                 }
4285                 if (_catch.bodyType().parameterTypes().size() != 1) {
4286                     throw new IllegalArgumentException("Catch should have zero parameters: " + _catch.bodyType());
4287                 }
4288             }
4289 
4290             if (finalizerC != null) {
4291                 this.finalizer = finalizerC.build(this);
4292                 if (!finalizer.bodyType().returnType().equals(VOID)) {
4293                     throw new IllegalArgumentException("Finally should return void: " + finalizer.bodyType());
4294                 }
4295                 if (!finalizer.bodyType().parameterTypes().isEmpty()) {
4296                     throw new IllegalArgumentException("Finally should have zero parameters: " + finalizer.bodyType());
4297                 }
4298             } else {
4299                 this.finalizer = null;
4300             }
4301         }
4302 
4303         @Override
4304         public List<Body> bodies() {
4305             ArrayList<Body> bodies = new ArrayList<>();
4306             if (resources != null) {
4307                 bodies.add(resources);
4308             }
4309             bodies.add(body);
4310             bodies.addAll(catchers);
4311             if (finalizer != null) {
4312                 bodies.add(finalizer);
4313             }
4314             return bodies;
4315         }
4316 
4317         public Body resources() {
4318             return resources;
4319         }
4320 
4321         public Body body() {
4322             return body;
4323         }
4324 
4325         public List<Body> catchers() {
4326             return catchers;
4327         }
4328 
4329         public Body finalizer() {
4330             return finalizer;
4331         }
4332 
4333         @Override
4334         public Block.Builder lower(Block.Builder b, OpTransformer opT) {
4335             if (resources != null) {
4336                 throw new UnsupportedOperationException("Lowering of try-with-resources is unsupported");
4337             }
4338 
4339             Block.Builder exit = b.block();
4340             setBranchTarget(b.context(), this, new BranchTarget(exit, null));
4341 
4342             // Simple case with no catch and finally bodies
4343             if (catchers.isEmpty() && finalizer == null) {
4344                 b.transformBody(body, List.of(), (block, op) -> {
4345                     if (op instanceof CoreOp.YieldOp) {
4346                         block.op(branch(exit.successor()));
4347                     } else {
4348                         // @@@ Composition of lowerable ops
4349                         if (op instanceof Lowerable lop) {
4350                             block = lop.lower(block, opT);
4351                         } else {
4352                             block.op(op);
4353                         }
4354                     }
4355                     return block;
4356                 });
4357                 return exit;
4358             }
4359 
4360             Block.Builder tryRegionEnter = b.block();
4361             Block.Builder tryRegionExit = b.block();
4362 
4363             // Construct the catcher block builders
4364             List<Block.Builder> catchers = catchers().stream()
4365                     .map(catcher -> b.block())
4366                     .toList();
4367             Block.Builder catcherFinally;
4368             if (finalizer == null) {
4369                 catcherFinally = null;
4370             } else {
4371                 catcherFinally = b.block();
4372                 catchers = new ArrayList<>(catchers);
4373                 catchers.add(catcherFinally);
4374             }
4375 
4376             // Enter the try exception region
4377             List<Block.Reference> exitHandlers = catchers.stream()
4378                     .map(Block.Builder::successor)
4379                     .toList();
4380             b.op(exceptionRegionEnter(tryRegionEnter.successor(), exitHandlers.reversed()));
4381 
4382             OpTransformer tryExitTransformer;
4383             if (finalizer != null) {
4384                 tryExitTransformer = opT.compose((block, op) -> {
4385                     if (op instanceof CoreOp.ReturnOp ||
4386                             (op instanceof JavaOp.JavaLabelOp lop && ifExitFromTry(lop))) {
4387                         return inlineFinalizer(block, exitHandlers, opT);
4388                     } else {
4389                         return block;
4390                     }
4391                 });
4392             } else {
4393                 tryExitTransformer = opT.compose((block, op) -> {
4394                     if (op instanceof CoreOp.ReturnOp ||
4395                             (op instanceof JavaOp.JavaLabelOp lop && ifExitFromTry(lop))) {
4396                         Block.Builder tryRegionReturnExit = block.block();
4397                         block.op(exceptionRegionExit(tryRegionReturnExit.successor(), exitHandlers));
4398                         return tryRegionReturnExit;
4399                     } else {
4400                         return block;
4401                     }
4402                 });
4403             }
4404             // Inline the try body
4405             AtomicBoolean hasTryRegionExit = new AtomicBoolean();
4406             tryRegionEnter.transformBody(body, List.of(), tryExitTransformer.andThen((block, op) -> {
4407                 if (op instanceof CoreOp.YieldOp) {
4408                     hasTryRegionExit.set(true);
4409                     block.op(branch(tryRegionExit.successor()));
4410                 } else {
4411                     // @@@ Composition of lowerable ops
4412                     if (op instanceof Lowerable lop) {
4413                         block = lop.lower(block, tryExitTransformer);
4414                     } else {
4415                         block.op(op);
4416                     }
4417                 }
4418                 return block;
4419             }));
4420 
4421             Block.Builder finallyEnter = null;
4422             if (finalizer != null) {
4423                 finallyEnter = b.block();
4424                 if (hasTryRegionExit.get()) {
4425                     // Exit the try exception region
4426                     tryRegionExit.op(exceptionRegionExit(finallyEnter.successor(), exitHandlers));
4427                 }
4428             } else if (hasTryRegionExit.get()) {
4429                 // Exit the try exception region
4430                 tryRegionExit.op(exceptionRegionExit(exit.successor(), exitHandlers));
4431             }
4432 
4433             // Inline the catch bodies
4434             for (int i = 0; i < this.catchers.size(); i++) {
4435                 Block.Builder catcher = catchers.get(i);
4436                 Body catcherBody = this.catchers.get(i);
4437                 // Create the throwable argument
4438                 Block.Parameter t = catcher.parameter(catcherBody.bodyType().parameterTypes().get(0));
4439 
4440                 if (finalizer != null) {
4441                     Block.Builder catchRegionEnter = b.block();
4442                     Block.Builder catchRegionExit = b.block();
4443 
4444                     // Enter the catch exception region
4445                     Result catchExceptionRegion = catcher.op(
4446                             exceptionRegionEnter(catchRegionEnter.successor(), catcherFinally.successor()));
4447 
4448                     OpTransformer catchExitTransformer = opT.compose((block, op) -> {
4449                         if (op instanceof CoreOp.ReturnOp) {
4450                             return inlineFinalizer(block, List.of(catcherFinally.successor()), opT);
4451                         } else if (op instanceof JavaOp.JavaLabelOp lop && ifExitFromTry(lop)) {
4452                             return inlineFinalizer(block, List.of(catcherFinally.successor()), opT);
4453                         } else {
4454                             return block;
4455                         }
4456                     });
4457                     // Inline the catch body
4458                     AtomicBoolean hasCatchRegionExit = new AtomicBoolean();
4459                     catchRegionEnter.transformBody(catcherBody, List.of(t), catchExitTransformer.andThen((block, op) -> {
4460                         if (op instanceof CoreOp.YieldOp) {
4461                             hasCatchRegionExit.set(true);
4462                             block.op(branch(catchRegionExit.successor()));
4463                         } else {
4464                             // @@@ Composition of lowerable ops
4465                             if (op instanceof Lowerable lop) {
4466                                 block = lop.lower(block, catchExitTransformer);
4467                             } else {
4468                                 block.op(op);
4469                             }
4470                         }
4471                         return block;
4472                     }));
4473 
4474                     // Exit the catch exception region
4475                     if (hasCatchRegionExit.get()) {
4476                         hasTryRegionExit.set(true);
4477                         catchRegionExit.op(exceptionRegionExit(finallyEnter.successor(), catcherFinally.successor()));
4478                     }
4479                 } else {
4480                     // Inline the catch body
4481                     catcher.transformBody(catcherBody, List.of(t), opT.andThen((block, op) -> {
4482                         if (op instanceof CoreOp.YieldOp) {
4483                             block.op(branch(exit.successor()));
4484                         } else {
4485                             // @@@ Composition of lowerable ops
4486                             if (op instanceof Lowerable lop) {
4487                                 block = lop.lower(block, opT);
4488                             } else {
4489                                 block.op(op);
4490                             }
4491                         }
4492                         return block;
4493                     }));
4494                 }
4495             }
4496 
4497             if (finalizer != null && hasTryRegionExit.get()) {
4498                 // Inline the finally body
4499                 finallyEnter.transformBody(finalizer, List.of(), opT.andThen((block, op) -> {
4500                     if (op instanceof CoreOp.YieldOp) {
4501                         block.op(branch(exit.successor()));
4502                     } else {
4503                         // @@@ Composition of lowerable ops
4504                         if (op instanceof Lowerable lop) {
4505                             block = lop.lower(block, opT);
4506                         } else {
4507                             block.op(op);
4508                         }
4509                     }
4510                     return block;
4511                 }));
4512             }
4513 
4514             // Inline the finally body as a catcher of Throwable and adjusting to throw
4515             if (finalizer != null) {
4516                 // Create the throwable argument
4517                 Block.Parameter t = catcherFinally.parameter(type(Throwable.class));
4518 
4519                 catcherFinally.transformBody(finalizer, List.of(), opT.andThen((block, op) -> {
4520                     if (op instanceof CoreOp.YieldOp) {
4521                         block.op(throw_(t));
4522                     } else {
4523                         // @@@ Composition of lowerable ops
4524                         if (op instanceof Lowerable lop) {
4525                             block = lop.lower(block, opT);
4526                         } else {
4527                             block.op(op);
4528                         }
4529                     }
4530                     return block;
4531                 }));
4532             }
4533             return exit;
4534         }
4535 
4536         boolean ifExitFromTry(JavaLabelOp lop) {
4537             Op target = lop.target();
4538             return target == this || target.isAncestorOf(this);
4539         }
4540 
4541         Block.Builder inlineFinalizer(Block.Builder block1, List<Block.Reference> tryHandlers, OpTransformer opT) {
4542             Block.Builder finallyEnter = block1.block();
4543             Block.Builder finallyExit = block1.block();
4544 
4545             block1.op(exceptionRegionExit(finallyEnter.successor(), tryHandlers));
4546 
4547             // Inline the finally body
4548             finallyEnter.transformBody(finalizer, List.of(), opT.andThen((block2, op2) -> {
4549                 if (op2 instanceof CoreOp.YieldOp) {
4550                     block2.op(branch(finallyExit.successor()));
4551                 } else {
4552                     // @@@ Composition of lowerable ops
4553                     if (op2 instanceof Lowerable lop2) {
4554                         block2 = lop2.lower(block2, opT);
4555                     } else {
4556                         block2.op(op2);
4557                     }
4558                 }
4559                 return block2;
4560             }));
4561 
4562             return finallyExit;
4563         }
4564 
4565         @Override
4566         public TypeElement resultType() {
4567             return VOID;
4568         }
4569     }
4570 
4571     //
4572     // Patterns
4573 
4574     // Reified pattern nodes
4575 
4576     /**
4577      * Synthetic pattern types
4578      * // @@@ Replace with types extending from TypeElement
4579      */
4580     public sealed interface Pattern {
4581 
4582         /**
4583          * Synthetic type pattern type.
4584          *
4585          * @param <T> the type of values that are bound
4586          */
4587         final class Type<T> implements Pattern {
4588             Type() {
4589             }
4590         }
4591 
4592         /**
4593          * Synthetic record pattern type.
4594          *
4595          * @param <T> the type of records that are bound
4596          */
4597         final class Record<T> implements Pattern {
4598             Record() {
4599             }
4600         }
4601 
4602         final class MatchAll implements Pattern {
4603             MatchAll() {
4604             }
4605         }
4606 
4607         // @@@ Pattern types
4608 
4609         JavaType PATTERN_BINDING_TYPE = JavaType.type(Type.class);
4610 
4611         JavaType PATTERN_RECORD_TYPE = JavaType.type(Record.class);
4612 
4613         JavaType PATTERN_MATCH_ALL_TYPE = JavaType.type(MatchAll.class);
4614 
4615         static JavaType bindingType(TypeElement t) {
4616             return parameterized(PATTERN_BINDING_TYPE, (JavaType) t);
4617         }
4618 
4619         static JavaType recordType(TypeElement t) {
4620             return parameterized(PATTERN_RECORD_TYPE, (JavaType) t);
4621         }
4622 
4623         static JavaType matchAllType() {
4624             return PATTERN_MATCH_ALL_TYPE;
4625         }
4626 
4627         static TypeElement targetType(TypeElement t) {
4628             return ((ClassType) t).typeArguments().get(0);
4629         }
4630     }
4631 
4632     /**
4633      * Pattern operations.
4634      */
4635     public static final class PatternOps {
4636         PatternOps() {
4637         }
4638 
4639         /**
4640          * The pattern operation.
4641          */
4642         public sealed static abstract class PatternOp extends JavaOp implements Op.Pure {
4643             PatternOp(PatternOp that, CopyContext cc) {
4644                 super(that, cc);
4645             }
4646 
4647             PatternOp(String name, List<Value> operands) {
4648                 super(name, operands);
4649             }
4650         }
4651 
4652         /**
4653          * The binding pattern operation, that can model Java language type patterns.
4654          */
4655         @OpDeclaration(TypePatternOp.NAME)
4656         public static final class TypePatternOp extends PatternOp {
4657             static final String NAME = "pattern.type";
4658 
4659             public static final String ATTRIBUTE_BINDING_NAME = NAME + ".binding.name";
4660 
4661             final TypeElement resultType;
4662             final String bindingName;
4663 
4664             TypePatternOp(ExternalizedOp def) {
4665                 super(NAME, List.of());
4666 
4667                 this.bindingName = def.extractAttributeValue(ATTRIBUTE_BINDING_NAME, true,
4668                         v -> switch (v) {
4669                             case String s -> s;
4670                             case null -> null;
4671                             default -> throw new UnsupportedOperationException("Unsupported pattern binding name value:" + v);
4672                         });
4673                 // @@@ Cannot use canonical constructor because it wraps the given type
4674                 this.resultType = def.resultType();
4675             }
4676 
4677             TypePatternOp(TypePatternOp that, CopyContext cc) {
4678                 super(that, cc);
4679 
4680                 this.bindingName = that.bindingName;
4681                 this.resultType = that.resultType;
4682             }
4683 
4684             @Override
4685             public TypePatternOp transform(CopyContext cc, OpTransformer ot) {
4686                 return new TypePatternOp(this, cc);
4687             }
4688 
4689             TypePatternOp(TypeElement targetType, String bindingName) {
4690                 super(NAME, List.of());
4691 
4692                 this.bindingName = bindingName;
4693                 this.resultType = Pattern.bindingType(targetType);
4694             }
4695 
4696             @Override
4697             public Map<String, Object> externalize() {
4698                 return bindingName == null ? Map.of() : Map.of("", bindingName);
4699             }
4700 
4701             public String bindingName() {
4702                 return bindingName;
4703             }
4704 
4705             public TypeElement targetType() {
4706                 return Pattern.targetType(resultType());
4707             }
4708 
4709             @Override
4710             public TypeElement resultType() {
4711                 return resultType;
4712             }
4713         }
4714 
4715         /**
4716          * The record pattern operation, that can model Java language record patterns.
4717          */
4718         @OpDeclaration(RecordPatternOp.NAME)
4719         public static final class RecordPatternOp extends PatternOp {
4720             static final String NAME = "pattern.record";
4721 
4722             public static final String ATTRIBUTE_RECORD_DESCRIPTOR = NAME + ".descriptor";
4723 
4724             final RecordTypeRef recordDescriptor;
4725 
4726             static RecordPatternOp create(ExternalizedOp def) {
4727                 RecordTypeRef recordDescriptor = def.extractAttributeValue(ATTRIBUTE_RECORD_DESCRIPTOR, true,
4728                         v -> switch (v) {
4729                             case RecordTypeRef rtd -> rtd;
4730                             case null, default ->
4731                                     throw new UnsupportedOperationException("Unsupported record type descriptor value:" + v);
4732                         });
4733 
4734                 return new RecordPatternOp(recordDescriptor, def.operands());
4735             }
4736 
4737             RecordPatternOp(RecordPatternOp that, CopyContext cc) {
4738                 super(that, cc);
4739 
4740                 this.recordDescriptor = that.recordDescriptor;
4741             }
4742 
4743             @Override
4744             public RecordPatternOp transform(CopyContext cc, OpTransformer ot) {
4745                 return new RecordPatternOp(this, cc);
4746             }
4747 
4748             RecordPatternOp(RecordTypeRef recordDescriptor, List<Value> nestedPatterns) {
4749                 // The type of each value is a subtype of Pattern
4750                 // The number of values corresponds to the number of components of the record
4751                 super(NAME, List.copyOf(nestedPatterns));
4752 
4753                 this.recordDescriptor = recordDescriptor;
4754             }
4755 
4756             @Override
4757             public Map<String, Object> externalize() {
4758                 return Map.of("", recordDescriptor());
4759             }
4760 
4761             public RecordTypeRef recordDescriptor() {
4762                 return recordDescriptor;
4763             }
4764 
4765             public TypeElement targetType() {
4766                 return Pattern.targetType(resultType());
4767             }
4768 
4769             @Override
4770             public TypeElement resultType() {
4771                 return Pattern.recordType(recordDescriptor.recordType());
4772             }
4773         }
4774 
4775         @OpDeclaration(MatchAllPatternOp.NAME)
4776         public static final class MatchAllPatternOp extends PatternOp {
4777 
4778             // @@@ we may need to add info about the type of the record component
4779             // this info can be used when lowering
4780 
4781             static final String NAME = "pattern.match.all";
4782 
4783             MatchAllPatternOp(ExternalizedOp def) {
4784                 this();
4785             }
4786 
4787             MatchAllPatternOp(MatchAllPatternOp that, CopyContext cc) {
4788                 super(that, cc);
4789             }
4790 
4791             MatchAllPatternOp() {
4792                 super(NAME, List.of());
4793             }
4794 
4795             @Override
4796             public Op transform(CopyContext cc, OpTransformer ot) {
4797                 return new MatchAllPatternOp(this, cc);
4798             }
4799 
4800             @Override
4801             public TypeElement resultType() {
4802                 return Pattern.matchAllType();
4803             }
4804         }
4805 
4806         /**
4807          * The match operation, that can model Java language pattern matching.
4808          */
4809         @OpDeclaration(MatchOp.NAME)
4810         public static final class MatchOp extends JavaOp implements Op.Isolated, Op.Lowerable {
4811             static final String NAME = "pattern.match";
4812 
4813             final Body pattern;
4814             final Body match;
4815 
4816             MatchOp(ExternalizedOp def) {
4817                 this(def.operands().get(0),
4818                         def.bodyDefinitions().get(0), def.bodyDefinitions().get(1));
4819             }
4820 
4821             MatchOp(MatchOp that, CopyContext cc, OpTransformer ot) {
4822                 super(that, cc);
4823 
4824                 this.pattern = that.pattern.transform(cc, ot).build(this);
4825                 this.match = that.match.transform(cc, ot).build(this);
4826             }
4827 
4828             @Override
4829             public MatchOp transform(CopyContext cc, OpTransformer ot) {
4830                 return new MatchOp(this, cc, ot);
4831             }
4832 
4833             MatchOp(Value target, Body.Builder patternC, Body.Builder matchC) {
4834                 super(NAME,
4835                         List.of(target));
4836 
4837                 this.pattern = patternC.build(this);
4838                 this.match = matchC.build(this);
4839             }
4840 
4841             @Override
4842             public List<Body> bodies() {
4843                 return List.of(pattern, match);
4844             }
4845 
4846             public Body pattern() {
4847                 return pattern;
4848             }
4849 
4850             public Body match() {
4851                 return match;
4852             }
4853 
4854             public Value target() {
4855                 return operands().get(0);
4856             }
4857 
4858             @Override
4859             public Block.Builder lower(Block.Builder b, OpTransformer opT) {
4860                 // No match block
4861                 Block.Builder endNoMatchBlock = b.block();
4862                 // Match block
4863                 Block.Builder endMatchBlock = b.block();
4864                 // End block
4865                 Block.Builder endBlock = b.block();
4866                 Block.Parameter matchResult = endBlock.parameter(resultType());
4867                 // Map match operation result
4868                 b.context().mapValue(result(), matchResult);
4869 
4870                 List<Value> patternValues = new ArrayList<>();
4871                 Op patternYieldOp = pattern.entryBlock().terminatingOp();
4872                 Op.Result rootPatternValue = (Op.Result) patternYieldOp.operands().get(0);
4873                 Block.Builder currentBlock = lower(endNoMatchBlock, b,
4874                         patternValues,
4875                         rootPatternValue.op(),
4876                         b.context().getValue(target()));
4877                 currentBlock.op(branch(endMatchBlock.successor()));
4878 
4879                 // No match block
4880                 // Pass false
4881                 endNoMatchBlock.op(branch(endBlock.successor(
4882                         endNoMatchBlock.op(constant(BOOLEAN, false)))));
4883 
4884                 // Match block
4885                 // Lower match body and pass true
4886                 endMatchBlock.transformBody(match, patternValues, opT.andThen((block, op) -> {
4887                     if (op instanceof CoreOp.YieldOp) {
4888                         block.op(branch(endBlock.successor(
4889                                 block.op(constant(BOOLEAN, true)))));
4890                     } else if (op instanceof Lowerable lop) {
4891                         // @@@ Composition of lowerable ops
4892                         block = lop.lower(block, opT);
4893                     } else {
4894                         block.op(op);
4895                     }
4896                     return block;
4897                 }));
4898 
4899                 return endBlock;
4900             }
4901 
4902             static Block.Builder lower(Block.Builder endNoMatchBlock, Block.Builder currentBlock,
4903                                        List<Value> bindings,
4904                                        Op pattern, Value target) {
4905                 return switch (pattern) {
4906                     case RecordPatternOp rp -> lowerRecordPattern(endNoMatchBlock, currentBlock, bindings, rp, target);
4907                     case TypePatternOp tp -> lowerTypePattern(endNoMatchBlock, currentBlock, bindings, tp, target);
4908                     case MatchAllPatternOp map -> lowerMatchAllPattern(currentBlock);
4909                     case null, default -> throw new UnsupportedOperationException("Unknown pattern op: " + pattern);
4910                 };
4911             }
4912 
4913             static Block.Builder lowerRecordPattern(Block.Builder endNoMatchBlock, Block.Builder currentBlock,
4914                                                     List<Value> bindings,
4915                                                     JavaOp.PatternOps.RecordPatternOp rpOp, Value target) {
4916                 TypeElement targetType = rpOp.targetType();
4917 
4918                 Block.Builder nextBlock = currentBlock.block();
4919 
4920                 // Check if instance of target type
4921                 Op.Result isInstance = currentBlock.op(instanceOf(targetType, target));
4922                 currentBlock.op(conditionalBranch(isInstance, nextBlock.successor(), endNoMatchBlock.successor()));
4923 
4924                 currentBlock = nextBlock;
4925 
4926                 target = currentBlock.op(cast(targetType, target));
4927 
4928                 // Access component values of record and match on each as nested target
4929                 List<Value> dArgs = rpOp.operands();
4930                 for (int i = 0; i < dArgs.size(); i++) {
4931                     Op.Result nestedPattern = (Op.Result) dArgs.get(i);
4932                     // @@@ Handle exceptions?
4933                     Value nestedTarget = currentBlock.op(invoke(rpOp.recordDescriptor().methodForComponent(i), target));
4934 
4935                     currentBlock = lower(endNoMatchBlock, currentBlock, bindings, nestedPattern.op(), nestedTarget);
4936                 }
4937 
4938                 return currentBlock;
4939             }
4940 
4941             static Block.Builder lowerTypePattern(Block.Builder endNoMatchBlock, Block.Builder currentBlock,
4942                                                   List<Value> bindings,
4943                                                   TypePatternOp tpOp, Value target) {
4944                 TypeElement targetType = tpOp.targetType();
4945 
4946                 // Check if instance of target type
4947                 Op p; // op that perform type check
4948                 Op c; // op that perform conversion
4949                 TypeElement s = target.type();
4950                 TypeElement t = targetType;
4951                 if (t instanceof PrimitiveType pt) {
4952                     if (s instanceof ClassType cs) {
4953                         // unboxing conversions
4954                         ClassType box;
4955                         if (cs.unbox().isEmpty()) { // s not a boxed type
4956                             // e.g. Number -> int, narrowing + unboxing
4957                             box = pt.box().orElseThrow();
4958                             p = instanceOf(box, target);
4959                         } else {
4960                             // e.g. Float -> float, unboxing
4961                             // e.g. Integer -> long, unboxing + widening
4962                             box = cs;
4963                             p = null;
4964                         }
4965                         c = invoke(MethodRef.method(box, t + "Value", t), target);
4966                     } else {
4967                         // primitive to primitive conversion
4968                         PrimitiveType ps = ((PrimitiveType) s);
4969                         if (isNarrowingPrimitiveConv(ps, pt) || isWideningPrimitiveConvWithCheck(ps, pt)
4970                                 || isWideningAndNarrowingPrimitiveConv(ps, pt)) {
4971                             // e.g. int -> byte, narrowing
4972                             // e,g. int -> float, widening with check
4973                             // e.g. byte -> char, widening and narrowing
4974                             MethodRef mref = convMethodRef(s, t);
4975                             p = invoke(mref, target);
4976                         } else {
4977                             p = null;
4978                         }
4979                         c = conv(targetType, target);
4980                     }
4981                 } else if (s instanceof PrimitiveType ps) {
4982                     // boxing conversions
4983                     // e.g. int -> Number, boxing + widening
4984                     // e.g. byte -> Byte, boxing
4985                     p = null;
4986                     ClassType box = ps.box().orElseThrow();
4987                     c = invoke(MethodRef.method(box, "valueOf", box, ps), target);
4988                 } else if (!s.equals(t)) {
4989                     // reference to reference, but not identity
4990                     // e.g. Number -> Double, narrowing
4991                     // e.g. Short -> Object, widening
4992                     p = instanceOf(targetType, target);
4993                     c = cast(targetType, target);
4994                 } else {
4995                     // identity reference
4996                     // e.g. Character -> Character
4997                     p = null;
4998                     c = null;
4999                 }
5000 
5001                 if (c != null) {
5002                     if (p != null) {
5003                         // p != null, we need to perform type check at runtime
5004                         Block.Builder nextBlock = currentBlock.block();
5005                         currentBlock.op(conditionalBranch(currentBlock.op(p), nextBlock.successor(), endNoMatchBlock.successor()));
5006                         currentBlock = nextBlock;
5007                     }
5008                     target = currentBlock.op(c);
5009                 }
5010 
5011                 bindings.add(target);
5012 
5013                 return currentBlock;
5014             }
5015 
5016             private static boolean isWideningAndNarrowingPrimitiveConv(PrimitiveType s, PrimitiveType t) {
5017                 return BYTE.equals(s) && CHAR.equals(t);
5018             }
5019 
5020             private static boolean isWideningPrimitiveConvWithCheck(PrimitiveType s, PrimitiveType t) {
5021                 return (INT.equals(s) && FLOAT.equals(t))
5022                         || (LONG.equals(s) && FLOAT.equals(t))
5023                         || (LONG.equals(s) && DOUBLE.equals(t));
5024             }
5025 
5026             // s -> t is narrowing if order(t) <= order(s)
5027             private final static Map<PrimitiveType, Integer> narrowingOrder = Map.of(
5028                     BYTE, 1,
5029                     SHORT, 2,
5030                     CHAR, 2,
5031                     INT, 3,
5032                     LONG, 4,
5033                     FLOAT, 5,
5034                     DOUBLE, 6
5035             );
5036             private static boolean isNarrowingPrimitiveConv(PrimitiveType s, PrimitiveType t) {
5037                 return narrowingOrder.get(t) <= narrowingOrder.get(s);
5038             }
5039 
5040             private static MethodRef convMethodRef(TypeElement s, TypeElement t) {
5041                 if (BYTE.equals(s) || SHORT.equals(s) || CHAR.equals(s)) {
5042                     s = INT;
5043                 }
5044                 String sn = capitalize(s.toString());
5045                 String tn = capitalize(t.toString());
5046                 String mn = "is%sTo%sExact".formatted(sn, tn);
5047                 JavaType exactConversionSupport = JavaType.type(ClassDesc.of("java.lang.runtime.ExactConversionsSupport"));
5048                 return MethodRef.method(exactConversionSupport, mn, BOOLEAN, s);
5049             }
5050 
5051             private static String capitalize(String s) {
5052                 return s.substring(0, 1).toUpperCase() + s.substring(1);
5053             }
5054 
5055             static Block.Builder lowerMatchAllPattern(Block.Builder currentBlock) {
5056                 return currentBlock;
5057             }
5058 
5059             @Override
5060             public TypeElement resultType() {
5061                 return BOOLEAN;
5062             }
5063         }
5064     }
5065 
5066     static Op createOp(ExternalizedOp def) {
5067         Op op = switch (def.name()) {
5068             case "add" -> new AddOp(def);
5069             case "and" -> new AndOp(def);
5070             case "array.length" -> new ArrayLengthOp(def);
5071             case "array.load" -> new ArrayAccessOp.ArrayLoadOp(def);
5072             case "array.store" -> new ArrayAccessOp.ArrayStoreOp(def);
5073             case "ashr" -> new AshrOp(def);
5074             case "assert" -> new AssertOp(def);
5075             case "cast" -> CastOp.create(def);
5076             case "compl" -> new ComplOp(def);
5077             case "concat" -> new ConcatOp(def);
5078             case "conv" -> new ConvOp(def);
5079             case "div" -> new DivOp(def);
5080             case "eq" -> new EqOp(def);
5081             case "exception.region.enter" -> new ExceptionRegionEnter(def);
5082             case "exception.region.exit" -> new ExceptionRegionExit(def);
5083             case "field.load" -> FieldAccessOp.FieldLoadOp.create(def);
5084             case "field.store" -> FieldAccessOp.FieldStoreOp.create(def);
5085             case "ge" -> new GeOp(def);
5086             case "gt" -> new GtOp(def);
5087             case "instanceof" -> InstanceOfOp.create(def);
5088             case "invoke" -> InvokeOp.create(def);
5089             case "java.block" -> new BlockOp(def);
5090             case "java.break" -> new BreakOp(def);
5091             case "java.cand" -> new ConditionalAndOp(def);
5092             case "java.cexpression" -> new ConditionalExpressionOp(def);
5093             case "java.continue" -> new ContinueOp(def);
5094             case "java.cor" -> new ConditionalOrOp(def);
5095             case "java.do.while" -> new DoWhileOp(def);
5096             case "java.enhancedFor" -> EnhancedForOp.create(def);
5097             case "java.for" -> ForOp.create(def);
5098             case "java.if" -> new IfOp(def);
5099             case "java.labeled" -> new LabeledOp(def);
5100             case "java.switch.expression" -> new SwitchExpressionOp(def);
5101             case "java.switch.fallthrough" -> new SwitchFallthroughOp(def);
5102             case "java.switch.statement" -> new SwitchStatementOp(def);
5103             case "java.synchronized" -> new SynchronizedOp(def);
5104             case "java.try" -> TryOp.create(def);
5105             case "java.while" -> new WhileOp(def);
5106             case "java.yield" -> new YieldOp(def);
5107             case "lambda" -> new LambdaOp(def);
5108             case "le" -> new LeOp(def);
5109             case "lshl" -> new LshlOp(def);
5110             case "lshr" -> new LshrOp(def);
5111             case "lt" -> new LtOp(def);
5112             case "mod" -> new ModOp(def);
5113             case "monitor.enter" -> new MonitorOp.MonitorEnterOp(def);
5114             case "monitor.exit" -> new MonitorOp.MonitorExitOp(def);
5115             case "mul" -> new MulOp(def);
5116             case "neg" -> new NegOp(def);
5117             case "neq" -> new NeqOp(def);
5118             case "new" -> NewOp.create(def);
5119             case "not" -> new NotOp(def);
5120             case "or" -> new OrOp(def);
5121             case "pattern.match" -> new PatternOps.MatchOp(def);
5122             case "pattern.match.all" -> new PatternOps.MatchAllPatternOp(def);
5123             case "pattern.record" -> PatternOps.RecordPatternOp.create(def);
5124             case "pattern.type" -> new PatternOps.TypePatternOp(def);
5125             case "sub" -> new SubOp(def);
5126             case "throw" -> new ThrowOp(def);
5127             case "xor" -> new XorOp(def);
5128             default -> null;
5129         };
5130         if (op != null) {
5131             op.setLocation(def.location());
5132         }
5133         return op;
5134     }
5135 
5136     /**
5137      * An operation factory for core operations composed with Java operations.
5138      */
5139     public static final OpFactory JAVA_OP_FACTORY = CoreOp.CORE_OP_FACTORY.andThen(JavaOp::createOp);
5140 
5141     /**
5142      * A Java dialect factory, for constructing core and Java operations and constructing
5143      * core type and Java type elements, where the core type elements can refer to Java
5144      * type elements.
5145      */
5146     public static final DialectFactory JAVA_DIALECT_FACTORY = new DialectFactory(
5147             JAVA_OP_FACTORY,
5148             JAVA_TYPE_FACTORY);
5149 
5150     /**
5151      * Creates a lambda operation.
5152      *
5153      * @param ancestorBody        the ancestor of the body of the lambda operation
5154      * @param funcType            the lambda operation's function type
5155      * @param functionalInterface the lambda operation's functional interface type
5156      * @return the lambda operation
5157      */
5158     public static LambdaOp.Builder lambda(Body.Builder ancestorBody,
5159                                           FunctionType funcType, TypeElement functionalInterface) {
5160         return new LambdaOp.Builder(ancestorBody, funcType, functionalInterface);
5161     }
5162 
5163     /**
5164      * Creates a lambda operation.
5165      *
5166      * @param functionalInterface the lambda operation's functional interface type
5167      * @param body                the body of the lambda operation
5168      * @return the lambda operation
5169      */
5170     public static LambdaOp lambda(TypeElement functionalInterface, Body.Builder body) {
5171         return new LambdaOp(functionalInterface, body);
5172     }
5173 
5174     /**
5175      * Creates an exception region enter operation
5176      *
5177      * @param start    the exception region block
5178      * @param catchers the blocks handling exceptions thrown by the region block
5179      * @return the exception region enter operation
5180      */
5181     public static ExceptionRegionEnter exceptionRegionEnter(Block.Reference start, Block.Reference... catchers) {
5182         return exceptionRegionEnter(start, List.of(catchers));
5183     }
5184 
5185     /**
5186      * Creates an exception region enter operation
5187      *
5188      * @param start    the exception region block
5189      * @param catchers the blocks handling exceptions thrown by the region block
5190      * @return the exception region enter operation
5191      */
5192     public static ExceptionRegionEnter exceptionRegionEnter(Block.Reference start, List<Block.Reference> catchers) {
5193         List<Block.Reference> s = new ArrayList<>();
5194         s.add(start);
5195         s.addAll(catchers);
5196         return new ExceptionRegionEnter(s);
5197     }
5198 
5199     /**
5200      * Creates an exception region exit operation
5201      *
5202      * @param end             the block to which control is transferred after the exception region is exited
5203      * @param catchers the blocks handling exceptions thrown by the region block
5204      * @return the exception region exit operation
5205      */
5206     public static ExceptionRegionExit exceptionRegionExit(Block.Reference end, Block.Reference... catchers) {
5207         return exceptionRegionExit(end, List.of(catchers));
5208     }
5209 
5210     /**
5211      * Creates an exception region exit operation
5212      *
5213      * @param end             the block to which control is transferred after the exception region is exited
5214      * @param catchers the blocks handling exceptions thrown by the region block
5215      * @return the exception region exit operation
5216      */
5217     public static ExceptionRegionExit exceptionRegionExit(Block.Reference end, List<Block.Reference> catchers) {
5218         List<Block.Reference> s = new ArrayList<>();
5219         s.add(end);
5220         s.addAll(catchers);
5221         return new ExceptionRegionExit(s);
5222     }
5223 
5224     /**
5225      * Creates a throw operation.
5226      *
5227      * @param exceptionValue the thrown value
5228      * @return the throw operation
5229      */
5230     public static ThrowOp throw_(Value exceptionValue) {
5231         return new ThrowOp(exceptionValue);
5232     }
5233 
5234     /**
5235      * Creates an assert operation.
5236      *
5237      * @param bodies the nested bodies
5238      * @return the assert operation
5239      */
5240     public static AssertOp assert_(List<Body.Builder> bodies) {
5241         return new AssertOp(bodies);
5242     }
5243 
5244     public static MonitorOp.MonitorEnterOp monitorEnter(Value monitor) {
5245         return new MonitorOp.MonitorEnterOp(monitor);
5246     }
5247 
5248     public static MonitorOp.MonitorExitOp monitorExit(Value monitor) {
5249         return new MonitorOp.MonitorExitOp(monitor);
5250     }
5251 
5252     /**
5253      * Creates an invoke operation modeling an invocation to an
5254      * instance or static (class) method with no variable arguments.
5255      * <p>
5256      * The invoke kind of the invoke operation is determined by
5257      * comparing the argument count with the invoke descriptor's
5258      * parameter count. If they are equal then the invoke kind is
5259      * {@link InvokeOp.InvokeKind#STATIC static}. If the parameter count
5260      * plus one is equal to the argument count then the invoke kind
5261      * is {@link InvokeOp.InvokeKind#STATIC instance}.
5262      * <p>
5263      * The invoke return type is the invoke descriptors return type.
5264      *
5265      * @param invokeDescriptor the invoke descriptor
5266      * @param args             the invoke parameters
5267      * @return the invoke operation
5268      */
5269     public static InvokeOp invoke(MethodRef invokeDescriptor, Value... args) {
5270         return invoke(invokeDescriptor, List.of(args));
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 arguments
5288      * @return the invoke operation
5289      */
5290     public static InvokeOp invoke(MethodRef invokeDescriptor, List<Value> args) {
5291         return invoke(invokeDescriptor.type().returnType(), invokeDescriptor, 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      *
5305      * @param returnType       the invoke return type
5306      * @param invokeDescriptor the invoke descriptor
5307      * @param args             the invoke arguments
5308      * @return the invoke operation
5309      */
5310     public static InvokeOp invoke(TypeElement returnType, MethodRef invokeDescriptor, Value... args) {
5311         return invoke(returnType, invokeDescriptor, List.of(args));
5312     }
5313 
5314     /**
5315      * Creates an invoke operation modeling an invocation to an
5316      * instance or static (class) method with no variable arguments.
5317      * <p>
5318      * The invoke kind of the invoke operation is determined by
5319      * comparing the argument count with the invoke descriptor's
5320      * parameter count. If they are equal then the invoke kind is
5321      * {@link InvokeOp.InvokeKind#STATIC static}. If the parameter count
5322      * plus one is equal to the argument count then the invoke kind
5323      * is {@link InvokeOp.InvokeKind#STATIC instance}.
5324      *
5325      * @param returnType       the invoke return type
5326      * @param invokeDescriptor the invoke descriptor
5327      * @param args             the invoke arguments
5328      * @return the invoke super operation
5329      */
5330     public static InvokeOp invoke(TypeElement returnType, MethodRef invokeDescriptor, List<Value> args) {
5331         int paramCount = invokeDescriptor.type().parameterTypes().size();
5332         int argCount = args.size();
5333         InvokeOp.InvokeKind ik = (argCount == paramCount + 1)
5334                 ? InvokeOp.InvokeKind.INSTANCE
5335                 : InvokeOp.InvokeKind.STATIC;
5336         return new InvokeOp(ik, false, returnType, invokeDescriptor, args);
5337     }
5338 
5339     /**
5340      * Creates an invoke operation modelling an invocation to a method.
5341      *
5342      * @param invokeKind       the invoke kind
5343      * @param isVarArgs        true if an invocation to a variable argument method
5344      * @param returnType       the return type
5345      * @param invokeDescriptor the invoke descriptor
5346      * @param args             the invoke arguments
5347      * @return the invoke operation
5348      * @throws IllegalArgumentException if there is a mismatch between the argument count
5349      *                                  and the invoke descriptors parameter count.
5350      */
5351     public static InvokeOp invoke(InvokeOp.InvokeKind invokeKind, boolean isVarArgs,
5352                                   TypeElement returnType, MethodRef invokeDescriptor, List<Value> args) {
5353         return new InvokeOp(invokeKind, isVarArgs, returnType, invokeDescriptor, args);
5354     }
5355 
5356     /**
5357      * Creates a conversion operation.
5358      *
5359      * @param to   the conversion target type
5360      * @param from the value to be converted
5361      * @return the conversion operation
5362      */
5363     public static ConvOp conv(TypeElement to, Value from) {
5364         return new ConvOp(to, from);
5365     }
5366 
5367     /**
5368      * Creates an instance creation operation.
5369      *
5370      * @param constructorDescriptor the constructor descriptor
5371      * @param args            the constructor arguments
5372      * @return the instance creation operation
5373      */
5374     public static NewOp new_(ConstructorRef constructorDescriptor, Value... args) {
5375         return new_(constructorDescriptor, List.of(args));
5376     }
5377 
5378     /**
5379      * Creates an instance creation operation.
5380      *
5381      * @param constructorDescriptor the constructor descriptor
5382      * @param args            the constructor arguments
5383      * @return the instance creation operation
5384      */
5385     public static NewOp new_(ConstructorRef constructorDescriptor, List<Value> args) {
5386         return new NewOp(false, constructorDescriptor.refType(), constructorDescriptor, args);
5387     }
5388 
5389     /**
5390      * Creates an instance creation operation.
5391      *
5392      * @param returnType      the instance type
5393      * @param constructorDescriptor the constructor descriptor
5394      * @param args            the constructor arguments
5395      * @return the instance creation operation
5396      */
5397     public static NewOp new_(TypeElement returnType, ConstructorRef constructorDescriptor,
5398                              Value... args) {
5399         return new_(returnType, constructorDescriptor, List.of(args));
5400     }
5401 
5402     /**
5403      * Creates an instance creation operation.
5404      *
5405      * @param returnType      the instance type
5406      * @param constructorDescriptor the constructor descriptor
5407      * @param args            the constructor arguments
5408      * @return the instance creation operation
5409      */
5410     public static NewOp new_(TypeElement returnType, ConstructorRef constructorDescriptor,
5411                              List<Value> args) {
5412         return new NewOp(false, returnType, constructorDescriptor, args);
5413     }
5414 
5415     /**
5416      * Creates an instance creation operation.
5417      *
5418      * @param returnType      the instance type
5419      * @param constructorDescriptor the constructor descriptor
5420      * @param args            the constructor arguments
5421      * @return the instance creation operation
5422      */
5423     public static NewOp new_(boolean isVarargs, TypeElement returnType, ConstructorRef constructorDescriptor,
5424                              List<Value> args) {
5425         return new NewOp(isVarargs, returnType, constructorDescriptor, args);
5426     }
5427 
5428     /**
5429      * Creates an array creation operation.
5430      *
5431      * @param arrayType the array type
5432      * @param length    the array size
5433      * @return the array creation operation
5434      */
5435     public static NewOp newArray(TypeElement arrayType, Value length) {
5436         ConstructorRef constructorDescriptor = ConstructorRef.constructor(arrayType, INT);
5437         return new_(constructorDescriptor, length);
5438     }
5439 
5440     /**
5441      * Creates a field load operation to a non-static field.
5442      *
5443      * @param descriptor the field descriptor
5444      * @param receiver   the receiver value
5445      * @return the field load operation
5446      */
5447     public static FieldAccessOp.FieldLoadOp fieldLoad(FieldRef descriptor, Value receiver) {
5448         return new FieldAccessOp.FieldLoadOp(descriptor.type(), descriptor, receiver);
5449     }
5450 
5451     /**
5452      * Creates a field load operation to a non-static field.
5453      *
5454      * @param resultType the result type of the operation
5455      * @param descriptor the field descriptor
5456      * @param receiver   the receiver value
5457      * @return the field load operation
5458      */
5459     public static FieldAccessOp.FieldLoadOp fieldLoad(TypeElement resultType, FieldRef descriptor, Value receiver) {
5460         return new FieldAccessOp.FieldLoadOp(resultType, descriptor, receiver);
5461     }
5462 
5463     /**
5464      * Creates a field load operation to a static field.
5465      *
5466      * @param descriptor the field descriptor
5467      * @return the field load operation
5468      */
5469     public static FieldAccessOp.FieldLoadOp fieldLoad(FieldRef descriptor) {
5470         return new FieldAccessOp.FieldLoadOp(descriptor.type(), descriptor);
5471     }
5472 
5473     /**
5474      * Creates a field load operation to a static field.
5475      *
5476      * @param resultType the result type of the operation
5477      * @param descriptor the field descriptor
5478      * @return the field load operation
5479      */
5480     public static FieldAccessOp.FieldLoadOp fieldLoad(TypeElement resultType, FieldRef descriptor) {
5481         return new FieldAccessOp.FieldLoadOp(resultType, descriptor);
5482     }
5483 
5484     /**
5485      * Creates a field store operation to a non-static field.
5486      *
5487      * @param descriptor the field descriptor
5488      * @param receiver   the receiver value
5489      * @param v          the value to store
5490      * @return the field store operation
5491      */
5492     public static FieldAccessOp.FieldStoreOp fieldStore(FieldRef descriptor, Value receiver, Value v) {
5493         return new FieldAccessOp.FieldStoreOp(descriptor, receiver, v);
5494     }
5495 
5496     /**
5497      * Creates a field load operation to a static field.
5498      *
5499      * @param descriptor the field descriptor
5500      * @param v          the value to store
5501      * @return the field store operation
5502      */
5503     public static FieldAccessOp.FieldStoreOp fieldStore(FieldRef descriptor, Value v) {
5504         return new FieldAccessOp.FieldStoreOp(descriptor, v);
5505     }
5506 
5507     /**
5508      * Creates an array length operation.
5509      *
5510      * @param array the array value
5511      * @return the array length operation
5512      */
5513     public static ArrayLengthOp arrayLength(Value array) {
5514         return new ArrayLengthOp(array);
5515     }
5516 
5517     /**
5518      * Creates an array load operation.
5519      *
5520      * @param array the array value
5521      * @param index the index value
5522      * @return the array load operation
5523      */
5524     public static ArrayAccessOp.ArrayLoadOp arrayLoadOp(Value array, Value index) {
5525         return new ArrayAccessOp.ArrayLoadOp(array, index);
5526     }
5527 
5528     /**
5529      * Creates an array load operation.
5530      *
5531      * @param array the array value
5532      * @param index the index value
5533      * @param componentType type of the array component
5534      * @return the array load operation
5535      */
5536     public static ArrayAccessOp.ArrayLoadOp arrayLoadOp(Value array, Value index, TypeElement componentType) {
5537         return new ArrayAccessOp.ArrayLoadOp(array, index, componentType);
5538     }
5539 
5540     /**
5541      * Creates an array store operation.
5542      *
5543      * @param array the array value
5544      * @param index the index value
5545      * @param v     the value to store
5546      * @return the array store operation
5547      */
5548     public static ArrayAccessOp.ArrayStoreOp arrayStoreOp(Value array, Value index, Value v) {
5549         return new ArrayAccessOp.ArrayStoreOp(array, index, v);
5550     }
5551 
5552     /**
5553      * Creates an instanceof operation.
5554      *
5555      * @param t the type to test against
5556      * @param v the value to test
5557      * @return the instanceof operation
5558      */
5559     public static InstanceOfOp instanceOf(TypeElement t, Value v) {
5560         return new InstanceOfOp(t, v);
5561     }
5562 
5563     /**
5564      * Creates a cast operation.
5565      *
5566      * @param resultType the result type of the operation
5567      * @param v          the value to cast
5568      * @return the cast operation
5569      */
5570     public static CastOp cast(TypeElement resultType, Value v) {
5571         return new CastOp(resultType, resultType, v);
5572     }
5573 
5574     /**
5575      * Creates a cast operation.
5576      *
5577      * @param resultType the result type of the operation
5578      * @param t          the type to cast to
5579      * @param v          the value to cast
5580      * @return the cast operation
5581      */
5582     public static CastOp cast(TypeElement resultType, JavaType t, Value v) {
5583         return new CastOp(resultType, t, v);
5584     }
5585 
5586     /**
5587      * Creates an add operation.
5588      *
5589      * @param lhs the first operand
5590      * @param rhs the second operand
5591      * @return the add operation
5592      */
5593     public static BinaryOp add(Value lhs, Value rhs) {
5594         return new AddOp(lhs, rhs);
5595     }
5596 
5597     /**
5598      * Creates a sub operation.
5599      *
5600      * @param lhs the first operand
5601      * @param rhs the second operand
5602      * @return the sub operation
5603      */
5604     public static BinaryOp sub(Value lhs, Value rhs) {
5605         return new SubOp(lhs, rhs);
5606     }
5607 
5608     /**
5609      * Creates a mul operation.
5610      *
5611      * @param lhs the first operand
5612      * @param rhs the second operand
5613      * @return the mul operation
5614      */
5615     public static BinaryOp mul(Value lhs, Value rhs) {
5616         return new MulOp(lhs, rhs);
5617     }
5618 
5619     /**
5620      * Creates a div operation.
5621      *
5622      * @param lhs the first operand
5623      * @param rhs the second operand
5624      * @return the div operation
5625      */
5626     public static BinaryOp div(Value lhs, Value rhs) {
5627         return new DivOp(lhs, rhs);
5628     }
5629 
5630     /**
5631      * Creates a mod operation.
5632      *
5633      * @param lhs the first operand
5634      * @param rhs the second operand
5635      * @return the mod operation
5636      */
5637     public static BinaryOp mod(Value lhs, Value rhs) {
5638         return new ModOp(lhs, rhs);
5639     }
5640 
5641     /**
5642      * Creates a bitwise/logical or operation.
5643      *
5644      * @param lhs the first operand
5645      * @param rhs the second operand
5646      * @return the or operation
5647      */
5648     public static BinaryOp or(Value lhs, Value rhs) {
5649         return new OrOp(lhs, rhs);
5650     }
5651 
5652     /**
5653      * Creates a bitwise/logical and operation.
5654      *
5655      * @param lhs the first operand
5656      * @param rhs the second operand
5657      * @return the and operation
5658      */
5659     public static BinaryOp and(Value lhs, Value rhs) {
5660         return new AndOp(lhs, rhs);
5661     }
5662 
5663     /**
5664      * Creates a bitwise/logical xor operation.
5665      *
5666      * @param lhs the first operand
5667      * @param rhs the second operand
5668      * @return the xor operation
5669      */
5670     public static BinaryOp xor(Value lhs, Value rhs) {
5671         return new XorOp(lhs, rhs);
5672     }
5673 
5674     /**
5675      * Creates a left shift operation.
5676      *
5677      * @param lhs the first operand
5678      * @param rhs the second operand
5679      * @return the xor operation
5680      */
5681     public static BinaryOp lshl(Value lhs, Value rhs) {
5682         return new LshlOp(lhs, rhs);
5683     }
5684 
5685     /**
5686      * Creates a right shift operation.
5687      *
5688      * @param lhs the first operand
5689      * @param rhs the second operand
5690      * @return the xor operation
5691      */
5692     public static BinaryOp ashr(Value lhs, Value rhs) {
5693         return new AshrOp(lhs, rhs);
5694     }
5695 
5696     /**
5697      * Creates an unsigned right shift operation.
5698      *
5699      * @param lhs the first operand
5700      * @param rhs the second operand
5701      * @return the xor operation
5702      */
5703     public static BinaryOp lshr(Value lhs, Value rhs) {
5704         return new LshrOp(lhs, rhs);
5705     }
5706 
5707     /**
5708      * Creates a neg operation.
5709      *
5710      * @param v the operand
5711      * @return the neg operation
5712      */
5713     public static UnaryOp neg(Value v) {
5714         return new NegOp(v);
5715     }
5716 
5717     /**
5718      * Creates a bitwise complement operation.
5719      *
5720      * @param v the operand
5721      * @return the bitwise complement operation
5722      */
5723     public static UnaryOp compl(Value v) {
5724         return new ComplOp(v);
5725     }
5726 
5727     /**
5728      * Creates a not operation.
5729      *
5730      * @param v the operand
5731      * @return the not operation
5732      */
5733     public static UnaryOp not(Value v) {
5734         return new NotOp(v);
5735     }
5736 
5737     /**
5738      * Creates an equals comparison operation.
5739      *
5740      * @param lhs the first operand
5741      * @param rhs the second operand
5742      * @return the equals comparison operation
5743      */
5744     public static BinaryTestOp eq(Value lhs, Value rhs) {
5745         return new EqOp(lhs, rhs);
5746     }
5747 
5748     /**
5749      * Creates a not equals comparison operation.
5750      *
5751      * @param lhs the first operand
5752      * @param rhs the second operand
5753      * @return the not equals comparison operation
5754      */
5755     public static BinaryTestOp neq(Value lhs, Value rhs) {
5756         return new NeqOp(lhs, rhs);
5757     }
5758 
5759     /**
5760      * Creates a greater than comparison operation.
5761      *
5762      * @param lhs the first operand
5763      * @param rhs the second operand
5764      * @return the greater than comparison operation
5765      */
5766     public static BinaryTestOp gt(Value lhs, Value rhs) {
5767         return new GtOp(lhs, rhs);
5768     }
5769 
5770     /**
5771      * Creates a greater than or equals to comparison operation.
5772      *
5773      * @param lhs the first operand
5774      * @param rhs the second operand
5775      * @return the greater than or equals to comparison operation
5776      */
5777     public static BinaryTestOp ge(Value lhs, Value rhs) {
5778         return new GeOp(lhs, rhs);
5779     }
5780 
5781     /**
5782      * Creates a less than comparison operation.
5783      *
5784      * @param lhs the first operand
5785      * @param rhs the second operand
5786      * @return the less than comparison operation
5787      */
5788     public static BinaryTestOp lt(Value lhs, Value rhs) {
5789         return new LtOp(lhs, rhs);
5790     }
5791 
5792     /**
5793      * Creates a less than or equals to comparison operation.
5794      *
5795      * @param lhs the first operand
5796      * @param rhs the second operand
5797      * @return the less than or equals to comparison operation
5798      */
5799     public static BinaryTestOp le(Value lhs, Value rhs) {
5800         return new LeOp(lhs, rhs);
5801     }
5802 
5803     /**
5804      * Creates a string concatenation operation.
5805      *
5806      * @param lhs the first operand
5807      * @param rhs the second operand
5808      * @return the string concatenation operation
5809      */
5810     public static ConcatOp concat(Value lhs, Value rhs) {
5811         return new ConcatOp(lhs, rhs);
5812     }
5813 
5814     /**
5815      * Creates a continue operation.
5816      *
5817      * @return the continue operation
5818      */
5819     public static ContinueOp continue_() {
5820         return continue_(null);
5821     }
5822 
5823     /**
5824      * Creates a continue operation.
5825      *
5826      * @param label the value associated with where to continue from
5827      * @return the continue operation
5828      */
5829     public static ContinueOp continue_(Value label) {
5830         return new ContinueOp(label);
5831     }
5832 
5833     /**
5834      * Creates a break operation.
5835      *
5836      * @return the break operation
5837      */
5838     public static BreakOp break_() {
5839         return break_(null);
5840     }
5841 
5842     /**
5843      * Creates a break operation.
5844      *
5845      * @param label the value associated with where to continue from
5846      * @return the break operation
5847      */
5848     public static BreakOp break_(Value label) {
5849         return new BreakOp(label);
5850     }
5851 
5852     /**
5853      * Creates a yield operation.
5854      *
5855      * @return the yield operation
5856      */
5857     public static YieldOp java_yield() {
5858         return java_yield(null);
5859     }
5860 
5861     /**
5862      * Creates a yield operation.
5863      *
5864      * @param operand the value to yield
5865      * @return the yield operation
5866      */
5867     public static YieldOp java_yield(Value operand) {
5868         return new YieldOp(operand);
5869     }
5870 
5871     /**
5872      * Creates a block operation.
5873      *
5874      * @param body the body builder of the operation to be built and become its child
5875      * @return the block operation
5876      */
5877     public static BlockOp block(Body.Builder body) {
5878         return new BlockOp(body);
5879     }
5880 
5881     /**
5882      * Creates a synchronized operation.
5883      *
5884      * @param expr the expression body builder of the operation to be built and become its child
5885      * @param blockBody the block body builder of the operation to be built and become its child
5886      * @return the synchronized operation
5887      */
5888     public static SynchronizedOp synchronized_(Body.Builder expr, Body.Builder blockBody) {
5889         return new SynchronizedOp(expr, blockBody);
5890     }
5891 
5892     /**
5893      * Creates a labeled 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 LabeledOp labeled(Body.Builder body) {
5899         return new LabeledOp(body);
5900     }
5901 
5902     /**
5903      * Creates an if operation builder.
5904      *
5905      * @param ancestorBody the nearest ancestor body builder from which to construct
5906      *                     body builders for this operation
5907      * @return the if operation builder
5908      */
5909     public static IfOp.IfBuilder if_(Body.Builder ancestorBody) {
5910         return new IfOp.IfBuilder(ancestorBody);
5911     }
5912 
5913     // Pairs of
5914     //   predicate ()boolean, body ()void
5915     // And one optional body ()void at the end
5916 
5917     /**
5918      * Creates an if operation.
5919      *
5920      * @param bodies the body builders of operation to be built and become its children
5921      * @return the if operation
5922      */
5923     public static IfOp if_(List<Body.Builder> bodies) {
5924         return new IfOp(bodies);
5925     }
5926 
5927     /**
5928      * Creates a switch expression operation.
5929      * <p>
5930      * The result type of the operation will be derived from the yield type of the second body
5931      *
5932      * @param target the switch target value
5933      * @param bodies the body builders of the operation to be built and become its children
5934      * @return the switch expression operation
5935      */
5936     public static SwitchExpressionOp switchExpression(Value target, List<Body.Builder> bodies) {
5937         return new SwitchExpressionOp(null, target, bodies);
5938     }
5939 
5940     /**
5941      * Creates a switch expression operation.
5942      *
5943      * @param resultType the result type of the expression
5944      * @param target     the switch target value
5945      * @param bodies     the body builders of the operation to be built and become its children
5946      * @return the switch expression operation
5947      */
5948     public static SwitchExpressionOp switchExpression(TypeElement resultType, Value target,
5949                                                       List<Body.Builder> bodies) {
5950         Objects.requireNonNull(resultType);
5951         return new SwitchExpressionOp(resultType, target, bodies);
5952     }
5953 
5954     /**
5955      * Creates a switch statement operation.
5956      * @param target the switch target value
5957      * @param bodies the body builders of the operation to be built and become its children
5958      * @return the switch statement operation
5959      */
5960     public static SwitchStatementOp switchStatement(Value target, List<Body.Builder> bodies) {
5961         return new SwitchStatementOp(target, bodies);
5962     }
5963 
5964     /**
5965      * Creates a switch fallthrough operation.
5966      *
5967      * @return the switch fallthrough operation
5968      */
5969     public static SwitchFallthroughOp switchFallthroughOp() {
5970         return new SwitchFallthroughOp();
5971     }
5972 
5973     /**
5974      * Creates a for operation builder.
5975      *
5976      * @param ancestorBody the nearest ancestor body builder from which to construct
5977      *                     body builders for this operation
5978      * @param initTypes    the types of initialized variables
5979      * @return the for operation builder
5980      */
5981     public static ForOp.InitBuilder for_(Body.Builder ancestorBody, TypeElement... initTypes) {
5982         return for_(ancestorBody, List.of(initTypes));
5983     }
5984 
5985     /**
5986      * Creates a for operation builder.
5987      *
5988      * @param ancestorBody the nearest ancestor body builder from which to construct
5989      *                     body builders for this operation
5990      * @param initTypes    the types of initialized variables
5991      * @return the for operation builder
5992      */
5993     public static ForOp.InitBuilder for_(Body.Builder ancestorBody, List<? extends TypeElement> initTypes) {
5994         return new ForOp.InitBuilder(ancestorBody, initTypes);
5995     }
5996 
5997 
5998     /**
5999      * Creates a for operation.
6000      *
6001      * @param init   the init body builder of the operation to be built and become its child
6002      * @param cond   the cond body builder of the operation to be built and become its child
6003      * @param update the update body builder of the operation to be built and become its child
6004      * @param body   the main body builder of the operation to be built and become its child
6005      * @return the for operation
6006      */
6007     // init ()Tuple<Var<T1>, Var<T2>, ..., Var<TN>>, or init ()void
6008     // cond (Var<T1>, Var<T2>, ..., Var<TN>)boolean
6009     // update (Var<T1>, Var<T2>, ..., Var<TN>)void
6010     // body (Var<T1>, Var<T2>, ..., Var<TN>)void
6011     public static ForOp for_(Body.Builder init,
6012                              Body.Builder cond,
6013                              Body.Builder update,
6014                              Body.Builder body) {
6015         return new ForOp(init, cond, update, body);
6016     }
6017 
6018     /**
6019      * Creates an enhanced for operation builder.
6020      *
6021      * @param ancestorBody the nearest ancestor body builder from which to construct
6022      *                     body builders for this operation
6023      * @param iterableType the iterable type
6024      * @param elementType  the element type
6025      * @return the enhanced for operation builder
6026      */
6027     public static EnhancedForOp.ExpressionBuilder enhancedFor(Body.Builder ancestorBody,
6028                                                               TypeElement iterableType, TypeElement elementType) {
6029         return new EnhancedForOp.ExpressionBuilder(ancestorBody, iterableType, elementType);
6030     }
6031 
6032     // expression ()I<E>
6033     // init (E )Var<T>
6034     // body (Var<T> )void
6035 
6036     /**
6037      * Creates an enhanced for operation.
6038      *
6039      * @param expression the expression body builder of the operation to be built and become its child
6040      * @param init       the init body builder of the operation to be built and become its child
6041      * @param body       the main body builder of the operation to be built and become its child
6042      * @return the enhanced for operation
6043      */
6044     public static EnhancedForOp enhancedFor(Body.Builder expression,
6045                                             Body.Builder init,
6046                                             Body.Builder body) {
6047         return new EnhancedForOp(expression, init, body);
6048     }
6049 
6050     /**
6051      * Creates a while operation builder.
6052      *
6053      * @param ancestorBody the nearest ancestor body builder from which to construct
6054      *                     body builders for this operation
6055      * @return the while operation builder
6056      */
6057     public static WhileOp.PredicateBuilder while_(Body.Builder ancestorBody) {
6058         return new WhileOp.PredicateBuilder(ancestorBody);
6059     }
6060 
6061     /**
6062      * Creates a while operation.
6063      *
6064      * @param predicate the predicate body builder of the operation to be built and become its child
6065      * @param body      the main body builder of the operation to be built and become its child
6066      * @return the while operation
6067      */
6068     // predicate, ()boolean, may be null for predicate returning true
6069     // body, ()void
6070     public static WhileOp while_(Body.Builder predicate, Body.Builder body) {
6071         return new WhileOp(predicate, body);
6072     }
6073 
6074     /**
6075      * Creates a do operation builder.
6076      *
6077      * @param ancestorBody the nearest ancestor body builder from which to construct
6078      *                     body builders for this operation
6079      * @return the do operation builder
6080      */
6081     public static DoWhileOp.BodyBuilder doWhile(Body.Builder ancestorBody) {
6082         return new DoWhileOp.BodyBuilder(ancestorBody);
6083     }
6084 
6085     /**
6086      * Creates a do operation.
6087      *
6088      * @param predicate the predicate body builder of the operation to be built and become its child
6089      * @param body      the main body builder of the operation to be built and become its child
6090      * @return the do operation
6091      */
6092     public static DoWhileOp doWhile(Body.Builder body, Body.Builder predicate) {
6093         return new DoWhileOp(body, predicate);
6094     }
6095 
6096     /**
6097      * Creates a conditional-and operation builder.
6098      *
6099      * @param ancestorBody the nearest ancestor body builder from which to construct
6100      *                     body builders for this operation
6101      * @param lhs          a consumer that builds the left-hand side body
6102      * @param rhs          a consumer that builds the right-hand side body
6103      * @return the conditional-and operation builder
6104      */
6105     public static ConditionalAndOp.Builder conditionalAnd(Body.Builder ancestorBody,
6106                                                           Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
6107         return new ConditionalAndOp.Builder(ancestorBody, lhs, rhs);
6108     }
6109 
6110     /**
6111      * Creates a conditional-or operation builder.
6112      *
6113      * @param ancestorBody the nearest ancestor body builder from which to construct
6114      *                     body builders for this operation
6115      * @param lhs          a consumer that builds the left-hand side body
6116      * @param rhs          a consumer that builds the right-hand side body
6117      * @return the conditional-or operation builder
6118      */
6119     public static ConditionalOrOp.Builder conditionalOr(Body.Builder ancestorBody,
6120                                                         Consumer<Block.Builder> lhs, Consumer<Block.Builder> rhs) {
6121         return new ConditionalOrOp.Builder(ancestorBody, lhs, rhs);
6122     }
6123 
6124     /**
6125      * Creates a conditional-and operation
6126      *
6127      * @param bodies the body builders of operation to be built and become its children
6128      * @return the conditional-and operation
6129      */
6130     // predicates, ()boolean
6131     public static ConditionalAndOp conditionalAnd(List<Body.Builder> bodies) {
6132         return new ConditionalAndOp(bodies);
6133     }
6134 
6135     /**
6136      * Creates a conditional-or operation
6137      *
6138      * @param bodies the body builders of operation to be built and become its children
6139      * @return the conditional-or operation
6140      */
6141     // predicates, ()boolean
6142     public static ConditionalOrOp conditionalOr(List<Body.Builder> bodies) {
6143         return new ConditionalOrOp(bodies);
6144     }
6145 
6146     /**
6147      * Creates a conditional operation
6148      *
6149      * @param expressionType the result type of the expression
6150      * @param bodies         the body builders of operation to be built and become its children
6151      * @return the conditional operation
6152      */
6153     public static ConditionalExpressionOp conditionalExpression(TypeElement expressionType,
6154                                                                 List<Body.Builder> bodies) {
6155         Objects.requireNonNull(expressionType);
6156         return new ConditionalExpressionOp(expressionType, bodies);
6157     }
6158 
6159     /**
6160      * Creates a conditional operation
6161      * <p>
6162      * The result type of the operation will be derived from the yield type of the second body
6163      *
6164      * @param bodies the body builders of operation to be built and become its children
6165      * @return the conditional operation
6166      */
6167     public static ConditionalExpressionOp conditionalExpression(List<Body.Builder> bodies) {
6168         return new ConditionalExpressionOp(null, bodies);
6169     }
6170 
6171     /**
6172      * Creates try operation builder.
6173      *
6174      * @param ancestorBody the nearest ancestor body builder from which to construct
6175      *                     body builders for this operation
6176      * @param c            a consumer that builds the try body
6177      * @return the try operation builder
6178      */
6179     public static TryOp.CatchBuilder try_(Body.Builder ancestorBody, Consumer<Block.Builder> c) {
6180         Body.Builder _try = Body.Builder.of(ancestorBody, CoreType.FUNCTION_TYPE_VOID);
6181         c.accept(_try.entryBlock());
6182         return new TryOp.CatchBuilder(ancestorBody, null, _try);
6183     }
6184 
6185     /**
6186      * Creates try-with-resources operation builder.
6187      *
6188      * @param ancestorBody the nearest ancestor body builder from which to construct
6189      *                     body builders for this operation
6190      * @param c            a consumer that builds the resources body
6191      * @return the try-with-resources operation builder
6192      */
6193     public static TryOp.BodyBuilder tryWithResources(Body.Builder ancestorBody,
6194                                                      List<? extends TypeElement> resourceTypes,
6195                                                      Consumer<Block.Builder> c) {
6196         resourceTypes = resourceTypes.stream().map(CoreType::varType).toList();
6197         Body.Builder resources = Body.Builder.of(ancestorBody,
6198                 CoreType.functionType(CoreType.tupleType(resourceTypes)));
6199         c.accept(resources.entryBlock());
6200         return new TryOp.BodyBuilder(ancestorBody, resourceTypes, resources);
6201     }
6202 
6203     // resources ()Tuple<Var<R1>, Var<R2>, ..., Var<RN>>, or null
6204     // try (Var<R1>, Var<R2>, ..., Var<RN>)void, or try ()void
6205     // catch (E )void, where E <: Throwable
6206     // finally ()void, or null
6207 
6208     /**
6209      * Creates a try or try-with-resources operation.
6210      *
6211      * @param resources the try body builder of the operation to be built and become its child,
6212      *                  may be null
6213      * @param body      the try body builder of the operation to be built and become its child
6214      * @param catchers  the catch body builders of the operation to be built and become its children
6215      * @param finalizer the finalizer body builder of the operation to be built and become its child
6216      * @return the try or try-with-resources operation
6217      */
6218     public static TryOp try_(Body.Builder resources,
6219                              Body.Builder body,
6220                              List<Body.Builder> catchers,
6221                              Body.Builder finalizer) {
6222         return new TryOp(resources, body, catchers, finalizer);
6223     }
6224 
6225     //
6226     // Patterns
6227 
6228     /**
6229      * Creates a pattern match operation.
6230      *
6231      * @param target  the target value
6232      * @param pattern the pattern body builder of the operation to be built and become its child
6233      * @param match   the match body builder of the operation to be built and become its child
6234      * @return the pattern match operation
6235      */
6236     public static PatternOps.MatchOp match(Value target,
6237                                            Body.Builder pattern, Body.Builder match) {
6238         return new PatternOps.MatchOp(target, pattern, match);
6239     }
6240 
6241     /**
6242      * Creates a pattern binding operation.
6243      *
6244      * @param type        the type of value to be bound
6245      * @param bindingName the binding name
6246      * @return the pattern binding operation
6247      */
6248     public static PatternOps.TypePatternOp typePattern(TypeElement type, String bindingName) {
6249         return new PatternOps.TypePatternOp(type, bindingName);
6250     }
6251 
6252     /**
6253      * Creates a record pattern operation.
6254      *
6255      * @param recordDescriptor the record descriptor
6256      * @param nestedPatterns   the nested pattern values
6257      * @return the record pattern operation
6258      */
6259     public static PatternOps.RecordPatternOp recordPattern(RecordTypeRef recordDescriptor, Value... nestedPatterns) {
6260         return recordPattern(recordDescriptor, List.of(nestedPatterns));
6261     }
6262 
6263     /**
6264      * Creates a record pattern operation.
6265      *
6266      * @param recordDescriptor the record descriptor
6267      * @param nestedPatterns   the nested pattern values
6268      * @return the record pattern operation
6269      */
6270     public static PatternOps.RecordPatternOp recordPattern(RecordTypeRef recordDescriptor, List<Value> nestedPatterns) {
6271         return new PatternOps.RecordPatternOp(recordDescriptor, nestedPatterns);
6272     }
6273 
6274     public static PatternOps.MatchAllPatternOp matchAllPattern() {
6275         return new PatternOps.MatchAllPatternOp();
6276     }
6277 }