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