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