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