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