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